diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/DtoProjectionTransformerDelegate.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/DtoProjectionTransformerDelegate.java index d57a83ab9..fd362c1e4 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/DtoProjectionTransformerDelegate.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/DtoProjectionTransformerDelegate.java @@ -42,7 +42,7 @@ class DtoProjectionTransformerDelegate { public QueryTokenStream transformSelectionList(QueryTokenStream selectionList) { if (!returnedType.isProjecting() || returnedType.getReturnedType().isInterface() - || selectionList.stream().anyMatch(it -> it.equals(TOKEN_NEW))) { + || !returnedType.needsCustomConstruction() || selectionList.stream().anyMatch(it -> it.equals(TOKEN_NEW))) { return selectionList; } diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/AbstractDtoQueryTransformerUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/AbstractDtoQueryTransformerUnitTests.java new file mode 100644 index 000000000..e72ded4fc --- /dev/null +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/AbstractDtoQueryTransformerUnitTests.java @@ -0,0 +1,133 @@ +/* + * Copyright 2024-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jpa.repository.query; + +import static org.assertj.core.api.Assertions.*; + +import java.lang.reflect.Method; + +import org.antlr.v4.runtime.tree.ParseTreeVisitor; +import org.junit.jupiter.api.Test; + +import org.springframework.data.jpa.provider.PersistenceProvider; +import org.springframework.data.projection.SpelAwareProxyProjectionFactory; +import org.springframework.data.repository.Repository; +import org.springframework.data.repository.core.support.DefaultRepositoryMetadata; +import org.springframework.data.repository.query.QueryMethod; + +/** + * Support class for unit tests for {@link DtoProjectionTransformerDelegate}. + * + * @author Mark Paluch + */ +abstract class AbstractDtoQueryTransformerUnitTests

> { + + JpaQueryMethod method = getMethod("dtoProjection"); + + @Test // GH-3076 + void shouldTranslateSingleProjectionToDto() { + + P parser = parse("SELECT p from Person p"); + + QueryTokenStream visit = getTransformer(parser).visit(parser.getContext()); + + assertThat(QueryRenderer.TokenRenderer.render(visit)).isEqualTo( + "SELECT new org.springframework.data.jpa.repository.query.AbstractDtoQueryTransformerUnitTests$MyRecord(p.foo, p.bar) from Person p"); + } + + @Test // GH-3076 + void shouldRewriteQueriesWithSubselect() { + + P parser = parse("select u from User u left outer join u.roles r where r in (select r from Role r)"); + + QueryTokenStream visit = getTransformer(parser).visit(parser.getContext()); + + assertThat(QueryRenderer.TokenRenderer.render(visit)).isEqualTo( + "select new org.springframework.data.jpa.repository.query.AbstractDtoQueryTransformerUnitTests$MyRecord(u.foo, u.bar) from User u left outer join u.roles r where r in (select r from Role r)"); + } + + @Test // GH-3076 + void shouldNotRewriteQueriesWithoutProperties() { + + JpaQueryMethod method = getMethod("noProjection"); + P parser = parse("select u from User u"); + + QueryTokenStream visit = getTransformer(parser, method).visit(parser.getContext()); + + assertThat(QueryRenderer.TokenRenderer.render(visit)).isEqualTo("select u from User u"); + } + + @Test // GH-3076 + void shouldNotTranslateConstructorExpressionQuery() { + + P parser = parse("SELECT NEW com.foo(p) from Person p"); + + QueryTokenStream visit = getTransformer(parser).visit(parser.getContext()); + + assertThat(QueryRenderer.TokenRenderer.render(visit)).isEqualTo("SELECT NEW com.foo(p) from Person p"); + } + + @Test + void shouldTranslatePropertySelectionToDto() { + + P parser = parse("SELECT p.foo, p.bar, sum(p.age) from Person p"); + + QueryTokenStream visit = getTransformer(parser).visit(parser.getContext()); + + assertThat(QueryRenderer.TokenRenderer.render(visit)).isEqualTo( + "SELECT new org.springframework.data.jpa.repository.query.AbstractDtoQueryTransformerUnitTests$MyRecord(p.foo, p.bar, sum(p.age)) from Person p"); + } + + private JpaQueryMethod getMethod(String name, Class... parameterTypes) { + + try { + Method method = MyRepo.class.getMethod(name, parameterTypes); + PersistenceProvider persistenceProvider = PersistenceProvider.HIBERNATE; + + return new JpaQueryMethod(method, new DefaultRepositoryMetadata(MyRepo.class), + new SpelAwareProxyProjectionFactory(), persistenceProvider); + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } + } + + abstract P parse(String query); + + private ParseTreeVisitor getTransformer(P parser) { + return getTransformer(parser, method); + } + + abstract ParseTreeVisitor getTransformer(P parser, QueryMethod method); + + interface MyRepo extends Repository { + + MyRecord dtoProjection(); + + EmptyClass noProjection(); + } + + record Person(String id) { + + } + + record MyRecord(String foo, String bar) { + + } + + static class EmptyClass { + + } +} diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EqlDtoQueryTransformerUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EqlDtoQueryTransformerUnitTests.java index b8ac4f35a..967af726e 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EqlDtoQueryTransformerUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EqlDtoQueryTransformerUnitTests.java @@ -15,102 +15,26 @@ */ package org.springframework.data.jpa.repository.query; -import static org.assertj.core.api.Assertions.*; - -import java.lang.reflect.Method; - -import org.junit.jupiter.api.Test; +import org.antlr.v4.runtime.tree.ParseTreeVisitor; import org.springframework.data.domain.Sort; -import org.springframework.data.jpa.provider.PersistenceProvider; -import org.springframework.data.projection.SpelAwareProxyProjectionFactory; -import org.springframework.data.repository.Repository; -import org.springframework.data.repository.core.support.DefaultRepositoryMetadata; +import org.springframework.data.repository.query.QueryMethod; /** * Unit tests for {@link DtoProjectionTransformerDelegate}. * * @author Mark Paluch */ -class EqlDtoQueryTransformerUnitTests { +class EqlDtoQueryTransformerUnitTests extends AbstractDtoQueryTransformerUnitTests { - JpaQueryMethod method = getMethod("dtoProjection"); - - @Test // GH-3076 - void shouldTranslateSingleProjectionToDto() { - - JpaQueryEnhancer.EqlQueryParser parser = JpaQueryEnhancer.EqlQueryParser.parseQuery("SELECT p from Person p"); - - QueryTokenStream visit = getTransformer(parser).visit(parser.getContext()); - - assertThat(QueryRenderer.TokenRenderer.render(visit)).isEqualTo( - "SELECT new org.springframework.data.jpa.repository.query.EqlDtoQueryTransformerUnitTests$MyRecord(p.foo, p.bar) from Person p"); + @Override + JpaQueryEnhancer.EqlQueryParser parse(String query) { + return JpaQueryEnhancer.EqlQueryParser.parseQuery(query); } - @Test // GH-3076 - void shouldRewriteQueriesWithSubselect() { - - JpaQueryEnhancer.EqlQueryParser parser = JpaQueryEnhancer.EqlQueryParser - .parseQuery("select u from User u left outer join u.roles r where r in (select r from Role r)"); - - QueryTokenStream visit = getTransformer(parser).visit(parser.getContext()); - - assertThat(QueryRenderer.TokenRenderer.render(visit)).isEqualTo( - "select new org.springframework.data.jpa.repository.query.EqlDtoQueryTransformerUnitTests$MyRecord(u.foo, u.bar) from User u left outer join u.roles r where r in (select r from Role r)"); - } - - @Test // GH-3076 - void shouldNotTranslateConstructorExpressionQuery() { - - JpaQueryEnhancer.EqlQueryParser parser = JpaQueryEnhancer.EqlQueryParser - .parseQuery("SELECT NEW Foo(p) from Person p"); - - QueryTokenStream visit = getTransformer(parser).visit(parser.getContext()); - - assertThat(QueryRenderer.TokenRenderer.render(visit)).isEqualTo("SELECT NEW Foo(p) from Person p"); - } - - @Test - void shouldTranslatePropertySelectionToDto() { - - JpaQueryEnhancer.EqlQueryParser parser = JpaQueryEnhancer.EqlQueryParser - .parseQuery("SELECT p.foo, p.bar, sum(p.age) from Person p"); - - EqlSortedQueryTransformer transformer = getTransformer(parser); - QueryTokenStream visit = transformer.visit(parser.getContext()); - - assertThat(QueryRenderer.TokenRenderer.render(visit)).isEqualTo( - "SELECT new org.springframework.data.jpa.repository.query.EqlDtoQueryTransformerUnitTests$MyRecord(p.foo, p.bar, sum(p.age)) from Person p"); - } - - private JpaQueryMethod getMethod(String name, Class... parameterTypes) { - - try { - Method method = MyRepo.class.getMethod(name, parameterTypes); - PersistenceProvider persistenceProvider = PersistenceProvider.HIBERNATE; - - return new JpaQueryMethod(method, new DefaultRepositoryMetadata(MyRepo.class), - new SpelAwareProxyProjectionFactory(), persistenceProvider); - } catch (NoSuchMethodException e) { - throw new RuntimeException(e); - } - } - - private EqlSortedQueryTransformer getTransformer(JpaQueryEnhancer.EqlQueryParser parser) { + @Override + ParseTreeVisitor getTransformer(JpaQueryEnhancer.EqlQueryParser parser, QueryMethod method) { return new EqlSortedQueryTransformer(Sort.unsorted(), parser.getQueryInformation(), method.getResultProcessor().getReturnedType()); } - - interface MyRepo extends Repository { - - MyRecord dtoProjection(); - } - - record Person(String id) { - - } - - record MyRecord(String foo, String bar) { - - } } diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/HqlDtoQueryTransformerUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/HqlDtoQueryTransformerUnitTests.java index 9afdcd976..4ac9f6a9c 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/HqlDtoQueryTransformerUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/HqlDtoQueryTransformerUnitTests.java @@ -15,101 +15,27 @@ */ package org.springframework.data.jpa.repository.query; -import static org.assertj.core.api.Assertions.*; - -import java.lang.reflect.Method; - -import org.junit.jupiter.api.Test; +import org.antlr.v4.runtime.tree.ParseTreeVisitor; import org.springframework.data.domain.Sort; -import org.springframework.data.jpa.provider.PersistenceProvider; -import org.springframework.data.projection.SpelAwareProxyProjectionFactory; -import org.springframework.data.repository.Repository; -import org.springframework.data.repository.core.support.DefaultRepositoryMetadata; +import org.springframework.data.repository.query.QueryMethod; /** * Unit tests for {@link DtoProjectionTransformerDelegate}. * * @author Mark Paluch */ -class HqlDtoQueryTransformerUnitTests { +class HqlDtoQueryTransformerUnitTests extends AbstractDtoQueryTransformerUnitTests { - JpaQueryMethod method = getMethod("dtoProjection"); - - @Test // GH-3076 - void shouldTranslateSingleProjectionToDto() { - - JpaQueryEnhancer.HqlQueryParser parser = JpaQueryEnhancer.HqlQueryParser.parseQuery("SELECT p from Person p"); - - QueryTokenStream visit = getTransformer(parser).visit(parser.getContext()); - - assertThat(QueryRenderer.TokenRenderer.render(visit)).isEqualTo( - "SELECT new org.springframework.data.jpa.repository.query.HqlDtoQueryTransformerUnitTests$MyRecord(p.foo, p.bar) from Person p"); + @Override + JpaQueryEnhancer.HqlQueryParser parse(String query) { + return JpaQueryEnhancer.HqlQueryParser.parseQuery(query); } - @Test // GH-3076 - void shouldRewriteQueriesWithSubselect() { - - JpaQueryEnhancer.HqlQueryParser parser = JpaQueryEnhancer.HqlQueryParser - .parseQuery("select u from User u left outer join u.roles r where r in (select r from Role r)"); - - QueryTokenStream visit = getTransformer(parser).visit(parser.getContext()); - - assertThat(QueryRenderer.TokenRenderer.render(visit)).isEqualTo( - "select new org.springframework.data.jpa.repository.query.HqlDtoQueryTransformerUnitTests$MyRecord(u.foo, u.bar) from User u left outer join u.roles r where r in (select r from Role r)"); - } - - @Test // GH-3076 - void shouldNotTranslateConstructorExpressionQuery() { - - JpaQueryEnhancer.HqlQueryParser parser = JpaQueryEnhancer.HqlQueryParser - .parseQuery("SELECT NEW String(p) from Person p"); - - QueryTokenStream visit = getTransformer(parser).visit(parser.getContext()); - - assertThat(QueryRenderer.TokenRenderer.render(visit)).isEqualTo("SELECT NEW String(p) from Person p"); - } - - @Test - void shouldTranslatePropertySelectionToDto() { - - JpaQueryEnhancer.HqlQueryParser parser = JpaQueryEnhancer.HqlQueryParser - .parseQuery("SELECT p.foo, p.bar, sum(p.age) from Person p"); - - QueryTokenStream visit = getTransformer(parser).visit(parser.getContext()); - - assertThat(QueryRenderer.TokenRenderer.render(visit)).isEqualTo( - "SELECT new org.springframework.data.jpa.repository.query.HqlDtoQueryTransformerUnitTests$MyRecord(p.foo, p.bar, sum(p.age)) from Person p"); - } - - private JpaQueryMethod getMethod(String name, Class... parameterTypes) { - - try { - Method method = MyRepo.class.getMethod(name, parameterTypes); - PersistenceProvider persistenceProvider = PersistenceProvider.HIBERNATE; - - return new JpaQueryMethod(method, new DefaultRepositoryMetadata(MyRepo.class), - new SpelAwareProxyProjectionFactory(), persistenceProvider); - } catch (NoSuchMethodException e) { - throw new RuntimeException(e); - } - } - - private HqlSortedQueryTransformer getTransformer(JpaQueryEnhancer.HqlQueryParser parser) { + @Override + ParseTreeVisitor getTransformer(JpaQueryEnhancer.HqlQueryParser parser, QueryMethod method) { return new HqlSortedQueryTransformer(Sort.unsorted(), parser.getQueryInformation(), method.getResultProcessor().getReturnedType()); } - interface MyRepo extends Repository { - - MyRecord dtoProjection(); - } - - record Person(String id) { - - } - - record MyRecord(String foo, String bar) { - - } } diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpqlDtoQueryTransformerUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpqlDtoQueryTransformerUnitTests.java index d0c8fa130..a82aaf758 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpqlDtoQueryTransformerUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpqlDtoQueryTransformerUnitTests.java @@ -15,101 +15,27 @@ */ package org.springframework.data.jpa.repository.query; -import static org.assertj.core.api.Assertions.*; - -import java.lang.reflect.Method; - -import org.junit.jupiter.api.Test; +import org.antlr.v4.runtime.tree.ParseTreeVisitor; import org.springframework.data.domain.Sort; -import org.springframework.data.jpa.provider.PersistenceProvider; -import org.springframework.data.projection.SpelAwareProxyProjectionFactory; -import org.springframework.data.repository.Repository; -import org.springframework.data.repository.core.support.DefaultRepositoryMetadata; +import org.springframework.data.repository.query.QueryMethod; /** * Unit tests for {@link DtoProjectionTransformerDelegate}. * * @author Mark Paluch */ -class JpqlDtoQueryTransformerUnitTests { +class JpqlDtoQueryTransformerUnitTests extends AbstractDtoQueryTransformerUnitTests { - JpaQueryMethod method = getMethod("dtoProjection"); - - @Test // GH-3076 - void shouldTranslateSingleProjectionToDto() { - - JpaQueryEnhancer.JpqlQueryParser parser = JpaQueryEnhancer.JpqlQueryParser.parseQuery("SELECT p from Person p"); - - QueryTokenStream visit = getTransformer(parser).visit(parser.getContext()); - - assertThat(QueryRenderer.TokenRenderer.render(visit)).isEqualTo( - "SELECT new org.springframework.data.jpa.repository.query.JpqlDtoQueryTransformerUnitTests$MyRecord(p.foo, p.bar) from Person p"); + @Override + JpaQueryEnhancer.JpqlQueryParser parse(String query) { + return JpaQueryEnhancer.JpqlQueryParser.parseQuery(query); } - @Test // GH-3076 - void shouldRewriteQueriesWithSubselect() { - - JpaQueryEnhancer.JpqlQueryParser parser = JpaQueryEnhancer.JpqlQueryParser - .parseQuery("select u from User u left outer join u.roles r where r in (select r from Role r)"); - - QueryTokenStream visit = getTransformer(parser).visit(parser.getContext()); - - assertThat(QueryRenderer.TokenRenderer.render(visit)).isEqualTo( - "select new org.springframework.data.jpa.repository.query.JpqlDtoQueryTransformerUnitTests$MyRecord(u.foo, u.bar) from User u left outer join u.roles r where r in (select r from Role r)"); - } - - @Test // GH-3076 - void shouldNotTranslateConstructorExpressionQuery() { - - JpaQueryEnhancer.JpqlQueryParser parser = JpaQueryEnhancer.JpqlQueryParser - .parseQuery("SELECT NEW Foo(p) from Person p"); - - QueryTokenStream visit = getTransformer(parser).visit(parser.getContext()); - - assertThat(QueryRenderer.TokenRenderer.render(visit)).isEqualTo("SELECT NEW Foo(p) from Person p"); - } - - @Test - void shouldTranslatePropertySelectionToDto() { - - JpaQueryEnhancer.JpqlQueryParser parser = JpaQueryEnhancer.JpqlQueryParser - .parseQuery("SELECT p.foo, p.bar, sum(p.age) from Person p"); - - QueryTokenStream visit = getTransformer(parser).visit(parser.getContext()); - - assertThat(QueryRenderer.TokenRenderer.render(visit)).isEqualTo( - "SELECT new org.springframework.data.jpa.repository.query.JpqlDtoQueryTransformerUnitTests$MyRecord(p.foo, p.bar, sum(p.age)) from Person p"); - } - - private JpaQueryMethod getMethod(String name, Class... parameterTypes) { - - try { - Method method = MyRepo.class.getMethod(name, parameterTypes); - PersistenceProvider persistenceProvider = PersistenceProvider.HIBERNATE; - - return new JpaQueryMethod(method, new DefaultRepositoryMetadata(MyRepo.class), - new SpelAwareProxyProjectionFactory(), persistenceProvider); - } catch (NoSuchMethodException e) { - throw new RuntimeException(e); - } - } - - private JpqlSortedQueryTransformer getTransformer(JpaQueryEnhancer.JpqlQueryParser parser) { + @Override + ParseTreeVisitor getTransformer(JpaQueryEnhancer.JpqlQueryParser parser, QueryMethod method) { return new JpqlSortedQueryTransformer(Sort.unsorted(), parser.getQueryInformation(), method.getResultProcessor().getReturnedType()); } - interface MyRepo extends Repository { - - MyRecord dtoProjection(); - } - - record Person(String id) { - - } - - record MyRecord(String foo, String bar) { - - } }