18
18
import static java .util .Collections .*;
19
19
import static org .assertj .core .api .Assertions .*;
20
20
import static org .mockito .Mockito .*;
21
+ import static org .springframework .data .jpa .repository .query .QueryUtils .*;
21
22
22
23
import java .util .Collections ;
23
24
import java .util .List ;
38
39
import javax .persistence .criteria .From ;
39
40
import javax .persistence .criteria .Join ;
40
41
import javax .persistence .criteria .JoinType ;
42
+ import javax .persistence .criteria .Predicate ;
41
43
import javax .persistence .criteria .Root ;
42
44
import javax .persistence .spi .PersistenceProvider ;
43
45
import javax .persistence .spi .PersistenceProviderResolver ;
46
48
import org .junit .jupiter .api .Test ;
47
49
import org .junit .jupiter .api .extension .ExtendWith ;
48
50
import org .mockito .Mockito ;
49
-
50
51
import org .springframework .data .domain .Sort ;
51
52
import org .springframework .data .domain .Sort .Direction ;
52
53
import org .springframework .data .jpa .domain .sample .Category ;
@@ -139,8 +140,8 @@ void createsLeftJoinForNonOptionalToOneWithNestedOptional() {
139
140
CriteriaQuery <InvoiceItem > query = builder .createQuery (InvoiceItem .class );
140
141
Root <InvoiceItem > root = query .from (InvoiceItem .class );
141
142
142
- QueryUtils
143
- . toExpressionRecursively ( root , PropertyPath . from ( "invoice.order.customer.name" , InvoiceItem . class ), false );
143
+ QueryUtils . toExpressionRecursively ( root , PropertyPath . from ( "invoice.order.customer.name" , InvoiceItem . class ),
144
+ false );
144
145
145
146
assertThat (getInnerJoins (root )).hasSize (1 ); // join invoice
146
147
Join <?, ?> rootInnerJoin = getInnerJoins (root ).iterator ().next ();
@@ -162,8 +163,8 @@ void reusesLeftJoinForNonOptionalToOneWithNestedOptional() {
162
163
root .join ("invoice" , JoinType .LEFT ).join ("order" , JoinType .LEFT );
163
164
164
165
// when navigating through a path with nested optionals
165
- QueryUtils
166
- . toExpressionRecursively ( root , PropertyPath . from ( "invoice.order.customer.name" , InvoiceItem . class ), false );
166
+ QueryUtils . toExpressionRecursively ( root , PropertyPath . from ( "invoice.order.customer.name" , InvoiceItem . class ),
167
+ false );
167
168
168
169
// assert that existing joins are reused and no additional joins are created
169
170
assertThat (getInnerJoins (root )).isEmpty (); // no inner join invoice
@@ -185,8 +186,8 @@ void reusesInnerJoinForNonOptionalToOneWithNestedOptional() {
185
186
// given an existing inner join an nested optional
186
187
root .join ("invoice" ).join ("order" );
187
188
188
- QueryUtils
189
- . toExpressionRecursively ( root , PropertyPath . from ( "invoice.order.customer.name" , InvoiceItem . class ), false );
189
+ QueryUtils . toExpressionRecursively ( root , PropertyPath . from ( "invoice.order.customer.name" , InvoiceItem . class ),
190
+ false );
190
191
191
192
// assert that no useless left joins are created
192
193
assertThat (getInnerJoins (root )).hasSize (1 ); // join invoice
@@ -307,7 +308,7 @@ void toOrdersCanSortByJoinColumn() {
307
308
308
309
Sort sort = Sort .by (Direction .ASC , "age" );
309
310
310
- List <javax .persistence .criteria .Order > orders = QueryUtils . toOrders (sort , join , builder );
311
+ List <javax .persistence .criteria .Order > orders = toOrders (sort , join , builder );
311
312
312
313
assertThat (orders ).hasSize (1 );
313
314
}
@@ -329,6 +330,29 @@ void demonstrateDifferentBehavorOfGetJoin() {
329
330
assertThat (root .getJoins ()).hasSize (getNumberOfJoinsAfterCreatingAPath ());
330
331
}
331
332
333
+ @ Test // GH-2253
334
+ void orderByMustReuseAJoin () {
335
+
336
+ CriteriaBuilder builder = em .getCriteriaBuilder ();
337
+ CriteriaQuery <Category > query = builder .createQuery (Category .class );
338
+ Root <Category > root = query .from (Category .class );
339
+
340
+ // this represents what is done by the specification in the issue
341
+ //
342
+ query .distinct (true ); // this does not belong into a specification
343
+ root .fetch ("product" , JoinType .LEFT );
344
+ final Predicate idEquals1 = builder .equal (root .get ("id" ), 1L );
345
+ query .where (idEquals1 );
346
+
347
+ // Also order by a field of the entity referenced by the fetch from the "Specification"
348
+ Sort sort = Sort .by (Direction .ASC , "product.name" );
349
+
350
+ query .orderBy (toOrders (sort , root , builder ));
351
+
352
+ assertThat (root .getJoins ()).hasSize (1 );
353
+
354
+ }
355
+
332
356
int getNumberOfJoinsAfterCreatingAPath () {
333
357
return 0 ;
334
358
}
@@ -357,6 +381,7 @@ static class Merchant {
357
381
@ SuppressWarnings ("unused" )
358
382
static class Address {
359
383
@ Id String id ;
384
+ String name ;
360
385
@ OneToOne (mappedBy = "address" ) Merchant merchant ;
361
386
}
362
387
0 commit comments