2222import jakarta .persistence .EntityManagerFactory ;
2323import jakarta .persistence .Tuple ;
2424import jakarta .persistence .TypedQuery ;
25+ import jakarta .persistence .metamodel .EntityType ;
2526import jakarta .persistence .metamodel .Metamodel ;
2627
2728import java .lang .reflect .Method ;
2829import java .util .Collection ;
2930import java .util .List ;
3031import java .util .Optional ;
32+ import java .util .Set ;
3133
3234import org .junit .jupiter .api .BeforeEach ;
3335import org .junit .jupiter .api .Test ;
4244import org .springframework .data .domain .Page ;
4345import org .springframework .data .domain .PageRequest ;
4446import org .springframework .data .domain .Pageable ;
47+ import org .springframework .data .domain .Sort ;
4548import org .springframework .data .jpa .domain .sample .User ;
4649import org .springframework .data .jpa .provider .QueryExtractor ;
4750import org .springframework .data .jpa .repository .NativeQuery ;
5053import org .springframework .data .jpa .repository .sample .UserRepository ;
5154import org .springframework .data .projection .ProjectionFactory ;
5255import org .springframework .data .projection .SpelAwareProxyProjectionFactory ;
56+ import org .springframework .data .repository .Repository ;
5357import org .springframework .data .repository .core .RepositoryMetadata ;
58+ import org .springframework .data .repository .core .support .AbstractRepositoryMetadata ;
5459import org .springframework .data .repository .query .Param ;
5560import org .springframework .data .repository .query .RepositoryQuery ;
61+ import org .springframework .data .repository .query .ResultProcessor ;
5662import org .springframework .data .repository .query .ValueExpressionDelegate ;
57- import org .springframework .data .util .TypeInformation ;
5863import org .springframework .lang .Nullable ;
5964
6065/**
@@ -84,7 +89,7 @@ class SimpleJpaQueryUnitTests {
8489 @ Mock QueryExtractor extractor ;
8590 @ Mock jakarta .persistence .Query query ;
8691 @ Mock TypedQuery <Long > typedQuery ;
87- @ Mock RepositoryMetadata metadata ;
92+ RepositoryMetadata metadata ;
8893 @ Mock ParameterBinder binder ;
8994 @ Mock Metamodel metamodel ;
9095
@@ -100,12 +105,8 @@ void setUp() throws SecurityException, NoSuchMethodException {
100105 when (em .getEntityManagerFactory ()).thenReturn (emf );
101106 when (em .getDelegate ()).thenReturn (em );
102107 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 );
109110
110111 Method setUp = UserRepository .class .getMethod ("findByLastname" , String .class );
111112 method = new JpaQueryMethod (setUp , metadata , factory , extractor );
@@ -156,7 +157,6 @@ void discoversNativeQuery() throws Exception {
156157 assertThat (jpaQuery ).isInstanceOf (NativeJpaQuery .class );
157158
158159 when (em .createNativeQuery (anyString (), eq (User .class ))).thenReturn (query );
159- when (metadata .getReturnedDomainClass (method )).thenReturn ((Class ) User .class );
160160
161161 jpaQuery .createQuery (new JpaParametersParameterAccessor (queryMethod .getParameters (), new Object [] { "Matthews" }));
162162
@@ -176,7 +176,6 @@ void discoversNativeQueryFromNativeQueryInterface() throws Exception {
176176 assertThat (jpaQuery ).isInstanceOf (NativeJpaQuery .class );
177177
178178 when (em .createNativeQuery (anyString (), eq (User .class ))).thenReturn (query );
179- when (metadata .getReturnedDomainClass (method )).thenReturn ((Class ) User .class );
180179
181180 jpaQuery .createQuery (new JpaParametersParameterAccessor (queryMethod .getParameters (), new Object [] { "Matthews" }));
182181
@@ -239,10 +238,11 @@ void allowsCountQueryUsingParametersNotInOriginalQuery() throws Exception {
239238 when (em .createNativeQuery (anyString ())).thenReturn (query );
240239
241240 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 ());
243243
244244 jpaQuery .doCreateCountQuery (new JpaParametersParameterAccessor (jpaQuery .getQueryMethod ().getParameters (),
245- new Object []{ "data" , PageRequest .of (0 , 10 )}));
245+ new Object [] { "data" , PageRequest .of (0 , 10 ) }));
246246
247247 ArgumentCaptor <String > queryStringCaptor = ArgumentCaptor .forClass (String .class );
248248 verify (em ).createQuery (queryStringCaptor .capture (), eq (Long .class ));
@@ -263,6 +263,67 @@ void projectsWithManuallyDeclaredQuery() throws Exception {
263263 verify (em , times (2 )).createQuery (anyString ());
264264 }
265265
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+
266327 @ Test // DATAJPA-1307
267328 void jdbcStyleParametersOnlyAllowedInNativeQueries () throws Exception {
268329
@@ -296,7 +357,8 @@ private AbstractJpaQuery createJpaQuery(Method method) {
296357 return createJpaQuery (method , null );
297358 }
298359
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 ) {
300362
301363 return JpaQueryFactory .INSTANCE .fromMethodWithQueryString (queryMethod , em , queryString , countQueryString ,
302364 QueryRewriter .IdentityQueryRewriter .INSTANCE , ValueExpressionDelegate .create ());
@@ -305,10 +367,11 @@ private AbstractJpaQuery createJpaQuery(JpaQueryMethod queryMethod, @Nullable St
305367 private AbstractJpaQuery createJpaQuery (Method method , @ Nullable Optional <String > countQueryString ) {
306368
307369 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 ()));
309372 }
310373
311- interface SampleRepository {
374+ interface SampleRepository extends Repository < User , Long > {
312375
313376 @ Query (value = "SELECT u FROM User u WHERE u.lastname = ?1" , nativeQuery = true )
314377 List <User > findNativeByLastname (String lastname );
@@ -334,11 +397,25 @@ interface SampleRepository {
334397 @ Query ("select u from User u" )
335398 Collection <UserProjection > projectWithExplicitQuery ();
336399
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+
337414 @ Query (value = "select u from #{#entityName} u" , countQuery = "select count(u.id) from #{#entityName} u" )
338415 List <User > findAllWithExpressionInCountQuery (Pageable pageable );
339416
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}" )
342419 List <User > findAllWithBindingsOnlyInCountQuery (String arg0 , Pageable pageable );
343420
344421 // Typo in named parameter
@@ -347,4 +424,10 @@ interface SampleRepository {
347424 }
348425
349426 interface UserProjection {}
427+
428+ static class UnrelatedType {
429+
430+ public UnrelatedType (String name ) {}
431+
432+ }
350433}
0 commit comments