22
22
import jakarta .persistence .EntityManagerFactory ;
23
23
import jakarta .persistence .Tuple ;
24
24
import jakarta .persistence .TypedQuery ;
25
+ import jakarta .persistence .metamodel .EntityType ;
25
26
import jakarta .persistence .metamodel .Metamodel ;
26
27
27
28
import java .lang .reflect .Method ;
28
29
import java .util .Collection ;
29
30
import java .util .List ;
30
31
import java .util .Optional ;
32
+ import java .util .Set ;
31
33
32
34
import org .junit .jupiter .api .BeforeEach ;
33
35
import org .junit .jupiter .api .Test ;
42
44
import org .springframework .data .domain .Page ;
43
45
import org .springframework .data .domain .PageRequest ;
44
46
import org .springframework .data .domain .Pageable ;
47
+ import org .springframework .data .domain .Sort ;
45
48
import org .springframework .data .jpa .domain .sample .User ;
46
49
import org .springframework .data .jpa .provider .QueryExtractor ;
47
50
import org .springframework .data .jpa .repository .NativeQuery ;
50
53
import org .springframework .data .jpa .repository .sample .UserRepository ;
51
54
import org .springframework .data .projection .ProjectionFactory ;
52
55
import org .springframework .data .projection .SpelAwareProxyProjectionFactory ;
56
+ import org .springframework .data .repository .Repository ;
53
57
import org .springframework .data .repository .core .RepositoryMetadata ;
58
+ import org .springframework .data .repository .core .support .AbstractRepositoryMetadata ;
54
59
import org .springframework .data .repository .query .Param ;
55
60
import org .springframework .data .repository .query .RepositoryQuery ;
61
+ import org .springframework .data .repository .query .ResultProcessor ;
56
62
import org .springframework .data .repository .query .ValueExpressionDelegate ;
57
- import org .springframework .data .util .TypeInformation ;
58
63
import org .springframework .lang .Nullable ;
59
64
60
65
/**
@@ -84,7 +89,7 @@ class SimpleJpaQueryUnitTests {
84
89
@ Mock QueryExtractor extractor ;
85
90
@ Mock jakarta .persistence .Query query ;
86
91
@ Mock TypedQuery <Long > typedQuery ;
87
- @ Mock RepositoryMetadata metadata ;
92
+ RepositoryMetadata metadata ;
88
93
@ Mock ParameterBinder binder ;
89
94
@ Mock Metamodel metamodel ;
90
95
@@ -100,12 +105,8 @@ void setUp() throws SecurityException, NoSuchMethodException {
100
105
when (em .getEntityManagerFactory ()).thenReturn (emf );
101
106
when (em .getDelegate ()).thenReturn (em );
102
107
when (emf .createEntityManager ()).thenReturn (em );
103
- when (metadata .getRepositoryInterface ()).thenReturn ((Class ) SampleRepository .class );
104
- when (metadata .getDomainType ()).thenReturn ((Class ) User .class );
105
- when (metadata .getDomainTypeInformation ()).thenReturn ((TypeInformation ) TypeInformation .of (User .class ));
106
- when (metadata .getReturnedDomainClass (Mockito .any (Method .class ))).thenReturn ((Class ) User .class );
107
- when (metadata .getReturnType (Mockito .any (Method .class )))
108
- .thenAnswer (invocation -> TypeInformation .fromReturnTypeOf (invocation .getArgument (0 )));
108
+
109
+ metadata = AbstractRepositoryMetadata .getMetadata (SampleRepository .class );
109
110
110
111
Method setUp = UserRepository .class .getMethod ("findByLastname" , String .class );
111
112
method = new JpaQueryMethod (setUp , metadata , factory , extractor );
@@ -156,7 +157,6 @@ void discoversNativeQuery() throws Exception {
156
157
assertThat (jpaQuery ).isInstanceOf (NativeJpaQuery .class );
157
158
158
159
when (em .createNativeQuery (anyString (), eq (User .class ))).thenReturn (query );
159
- when (metadata .getReturnedDomainClass (method )).thenReturn ((Class ) User .class );
160
160
161
161
jpaQuery .createQuery (new JpaParametersParameterAccessor (queryMethod .getParameters (), new Object [] { "Matthews" }));
162
162
@@ -176,7 +176,6 @@ void discoversNativeQueryFromNativeQueryInterface() throws Exception {
176
176
assertThat (jpaQuery ).isInstanceOf (NativeJpaQuery .class );
177
177
178
178
when (em .createNativeQuery (anyString (), eq (User .class ))).thenReturn (query );
179
- when (metadata .getReturnedDomainClass (method )).thenReturn ((Class ) User .class );
180
179
181
180
jpaQuery .createQuery (new JpaParametersParameterAccessor (queryMethod .getParameters (), new Object [] { "Matthews" }));
182
181
@@ -239,10 +238,11 @@ void allowsCountQueryUsingParametersNotInOriginalQuery() throws Exception {
239
238
when (em .createNativeQuery (anyString ())).thenReturn (query );
240
239
241
240
AbstractJpaQuery jpaQuery = createJpaQuery (
242
- SampleRepository .class .getMethod ("findAllWithBindingsOnlyInCountQuery" , String .class , Pageable .class ), Optional .empty ());
241
+ SampleRepository .class .getMethod ("findAllWithBindingsOnlyInCountQuery" , String .class , Pageable .class ),
242
+ Optional .empty ());
243
243
244
244
jpaQuery .doCreateCountQuery (new JpaParametersParameterAccessor (jpaQuery .getQueryMethod ().getParameters (),
245
- new Object []{ "data" , PageRequest .of (0 , 10 )}));
245
+ new Object [] { "data" , PageRequest .of (0 , 10 ) }));
246
246
247
247
ArgumentCaptor <String > queryStringCaptor = ArgumentCaptor .forClass (String .class );
248
248
verify (em ).createQuery (queryStringCaptor .capture (), eq (Long .class ));
@@ -263,6 +263,67 @@ void projectsWithManuallyDeclaredQuery() throws Exception {
263
263
verify (em , times (2 )).createQuery (anyString ());
264
264
}
265
265
266
+ @ Test // GH-3895
267
+ void doesNotRewriteQueryReturningEntity () throws Exception {
268
+
269
+ EntityType <?> entityType = mock (EntityType .class );
270
+ when (entityType .getJavaType ()).thenReturn ((Class ) UnrelatedType .class );
271
+ when (metamodel .getManagedTypes ()).thenReturn (Set .of (entityType ));
272
+
273
+ AbstractStringBasedJpaQuery jpaQuery = (AbstractStringBasedJpaQuery ) createJpaQuery (
274
+ SampleRepository .class .getMethod ("selectWithJoin" ));
275
+
276
+ JpaParametersParameterAccessor accessor = new JpaParametersParameterAccessor (
277
+ jpaQuery .getQueryMethod ().getParameters (), new Object [0 ]);
278
+ ResultProcessor processor = jpaQuery .getQueryMethod ().getResultProcessor ().withDynamicProjection (accessor );
279
+ String queryString = jpaQuery .getSortedQueryString (Sort .unsorted (), jpaQuery .getReturnedType (processor ));
280
+
281
+ assertThat (queryString ).startsWith ("SELECT cd FROM CampaignDeal cd" );
282
+ }
283
+
284
+ @ Test // GH-3895
285
+ void rewriteQueryReturningDto () throws Exception {
286
+
287
+ AbstractStringBasedJpaQuery jpaQuery = (AbstractStringBasedJpaQuery ) createJpaQuery (
288
+ SampleRepository .class .getMethod ("selectWithJoin" ));
289
+
290
+ JpaParametersParameterAccessor accessor = new JpaParametersParameterAccessor (
291
+ jpaQuery .getQueryMethod ().getParameters (), new Object [0 ]);
292
+ ResultProcessor processor = jpaQuery .getQueryMethod ().getResultProcessor ().withDynamicProjection (accessor );
293
+ String queryString = jpaQuery .getSortedQueryString (Sort .unsorted (), jpaQuery .getReturnedType (processor ));
294
+
295
+ assertThat (queryString ).startsWith (
296
+ "SELECT new org.springframework.data.jpa.repository.query.SimpleJpaQueryUnitTests$UnrelatedType(cd.name)" );
297
+ }
298
+
299
+ @ Test // GH-3895
300
+ void doesNotRewriteQueryForUnknownProperty () throws Exception {
301
+
302
+ AbstractStringBasedJpaQuery jpaQuery = (AbstractStringBasedJpaQuery ) createJpaQuery (
303
+ SampleRepository .class .getMethod ("projectWithUnknownPaths" ));
304
+
305
+ JpaParametersParameterAccessor accessor = new JpaParametersParameterAccessor (
306
+ jpaQuery .getQueryMethod ().getParameters (), new Object [0 ]);
307
+ ResultProcessor processor = jpaQuery .getQueryMethod ().getResultProcessor ().withDynamicProjection (accessor );
308
+ String queryString = jpaQuery .getSortedQueryString (Sort .unsorted (), jpaQuery .getReturnedType (processor ));
309
+
310
+ assertThat (queryString ).startsWith ("select u.unknown from User u" );
311
+ }
312
+
313
+ @ Test // GH-3895
314
+ void doesNotRewriteQueryForJoinPath () throws Exception {
315
+
316
+ AbstractStringBasedJpaQuery jpaQuery = (AbstractStringBasedJpaQuery ) createJpaQuery (
317
+ SampleRepository .class .getMethod ("projectWithJoinPaths" ));
318
+
319
+ JpaParametersParameterAccessor accessor = new JpaParametersParameterAccessor (
320
+ jpaQuery .getQueryMethod ().getParameters (), new Object [0 ]);
321
+ ResultProcessor processor = jpaQuery .getQueryMethod ().getResultProcessor ().withDynamicProjection (accessor );
322
+ String queryString = jpaQuery .getSortedQueryString (Sort .unsorted (), jpaQuery .getReturnedType (processor ));
323
+
324
+ assertThat (queryString ).startsWith ("select r.name from User u LEFT JOIN FETCH u.roles r" );
325
+ }
326
+
266
327
@ Test // DATAJPA-1307
267
328
void jdbcStyleParametersOnlyAllowedInNativeQueries () throws Exception {
268
329
@@ -296,7 +357,8 @@ private AbstractJpaQuery createJpaQuery(Method method) {
296
357
return createJpaQuery (method , null );
297
358
}
298
359
299
- private AbstractJpaQuery createJpaQuery (JpaQueryMethod queryMethod , @ Nullable String queryString , @ Nullable String countQueryString ) {
360
+ private AbstractJpaQuery createJpaQuery (JpaQueryMethod queryMethod , @ Nullable String queryString ,
361
+ @ Nullable String countQueryString ) {
300
362
301
363
return JpaQueryFactory .INSTANCE .fromMethodWithQueryString (queryMethod , em , queryString , countQueryString ,
302
364
QueryRewriter .IdentityQueryRewriter .INSTANCE , ValueExpressionDelegate .create ());
@@ -305,10 +367,11 @@ private AbstractJpaQuery createJpaQuery(JpaQueryMethod queryMethod, @Nullable St
305
367
private AbstractJpaQuery createJpaQuery (Method method , @ Nullable Optional <String > countQueryString ) {
306
368
307
369
JpaQueryMethod queryMethod = new JpaQueryMethod (method , metadata , factory , extractor );
308
- return createJpaQuery (queryMethod , queryMethod .getAnnotatedQuery (), countQueryString == null ? null : countQueryString .orElse (queryMethod .getCountQuery ()));
370
+ return createJpaQuery (queryMethod , queryMethod .getAnnotatedQuery (),
371
+ countQueryString == null ? null : countQueryString .orElse (queryMethod .getCountQuery ()));
309
372
}
310
373
311
- interface SampleRepository {
374
+ interface SampleRepository extends Repository < User , Long > {
312
375
313
376
@ Query (value = "SELECT u FROM User u WHERE u.lastname = ?1" , nativeQuery = true )
314
377
List <User > findNativeByLastname (String lastname );
@@ -334,11 +397,25 @@ interface SampleRepository {
334
397
@ Query ("select u from User u" )
335
398
Collection <UserProjection > projectWithExplicitQuery ();
336
399
400
+ @ Query ("""
401
+ SELECT cd FROM CampaignDeal cd
402
+ LEFT JOIN FETCH cd.dealLibrary d
403
+ LEFT JOIN FETCH d.publisher p
404
+ WHERE cd.campaignId = :campaignId
405
+ """ )
406
+ Collection <UnrelatedType > selectWithJoin ();
407
+
408
+ @ Query ("select u.unknown from User u" )
409
+ Collection <UnrelatedType > projectWithUnknownPaths ();
410
+
411
+ @ Query ("select r.name from User u LEFT JOIN FETCH u.roles r" )
412
+ Collection <UnrelatedType > projectWithJoinPaths ();
413
+
337
414
@ Query (value = "select u from #{#entityName} u" , countQuery = "select count(u.id) from #{#entityName} u" )
338
415
List <User > findAllWithExpressionInCountQuery (Pageable pageable );
339
416
340
-
341
- @ Query ( value = "select u from User u" , countQuery = "select count(u.id) from #{#entityName} u where u.name = :#{#arg0}" )
417
+ @ Query ( value = "select u from User u" ,
418
+ countQuery = "select count(u.id) from #{#entityName} u where u.name = :#{#arg0}" )
342
419
List <User > findAllWithBindingsOnlyInCountQuery (String arg0 , Pageable pageable );
343
420
344
421
// Typo in named parameter
@@ -347,4 +424,10 @@ interface SampleRepository {
347
424
}
348
425
349
426
interface UserProjection {}
427
+
428
+ static class UnrelatedType {
429
+
430
+ public UnrelatedType (String name ) {}
431
+
432
+ }
350
433
}
0 commit comments