mirror of
https://github.com/spring-projects/spring-data-jpa.git
synced 2026-05-03 03:31:04 +01:00
Refrain from rewriting queries without input properties.
We now no longer attempt to rewrite the query if the target type doesn't define input properties (no-args constructor or multiple constructors). Closes #3895
This commit is contained in:
+1
-1
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+133
@@ -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<P extends JpaQueryEnhancer<? extends QueryInformation>> {
|
||||
|
||||
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<QueryTokenStream> getTransformer(P parser) {
|
||||
return getTransformer(parser, method);
|
||||
}
|
||||
|
||||
abstract ParseTreeVisitor<QueryTokenStream> getTransformer(P parser, QueryMethod method);
|
||||
|
||||
interface MyRepo extends Repository<Person, String> {
|
||||
|
||||
MyRecord dtoProjection();
|
||||
|
||||
EmptyClass noProjection();
|
||||
}
|
||||
|
||||
record Person(String id) {
|
||||
|
||||
}
|
||||
|
||||
record MyRecord(String foo, String bar) {
|
||||
|
||||
}
|
||||
|
||||
static class EmptyClass {
|
||||
|
||||
}
|
||||
}
|
||||
+8
-84
@@ -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<JpaQueryEnhancer.EqlQueryParser> {
|
||||
|
||||
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<QueryTokenStream> getTransformer(JpaQueryEnhancer.EqlQueryParser parser, QueryMethod method) {
|
||||
return new EqlSortedQueryTransformer(Sort.unsorted(), parser.getQueryInformation(),
|
||||
method.getResultProcessor().getReturnedType());
|
||||
}
|
||||
|
||||
interface MyRepo extends Repository<Person, String> {
|
||||
|
||||
MyRecord dtoProjection();
|
||||
}
|
||||
|
||||
record Person(String id) {
|
||||
|
||||
}
|
||||
|
||||
record MyRecord(String foo, String bar) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
+8
-82
@@ -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<JpaQueryEnhancer.HqlQueryParser> {
|
||||
|
||||
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<QueryTokenStream> getTransformer(JpaQueryEnhancer.HqlQueryParser parser, QueryMethod method) {
|
||||
return new HqlSortedQueryTransformer(Sort.unsorted(), parser.getQueryInformation(),
|
||||
method.getResultProcessor().getReturnedType());
|
||||
}
|
||||
|
||||
interface MyRepo extends Repository<Person, String> {
|
||||
|
||||
MyRecord dtoProjection();
|
||||
}
|
||||
|
||||
record Person(String id) {
|
||||
|
||||
}
|
||||
|
||||
record MyRecord(String foo, String bar) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
+8
-82
@@ -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<JpaQueryEnhancer.JpqlQueryParser> {
|
||||
|
||||
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<QueryTokenStream> getTransformer(JpaQueryEnhancer.JpqlQueryParser parser, QueryMethod method) {
|
||||
return new JpqlSortedQueryTransformer(Sort.unsorted(), parser.getQueryInformation(),
|
||||
method.getResultProcessor().getReturnedType());
|
||||
}
|
||||
|
||||
interface MyRepo extends Repository<Person, String> {
|
||||
|
||||
MyRecord dtoProjection();
|
||||
}
|
||||
|
||||
record Person(String id) {
|
||||
|
||||
}
|
||||
|
||||
record MyRecord(String foo, String bar) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user