Skip to content

Commit caa4704

Browse files
committed
Exclude DTO types without custom construction from DTO constructor rewriting.
We now verify that we can actually express a valid constructor expression before rewriting queries to use constructor expressions. See #3929
1 parent ab96acc commit caa4704

File tree

6 files changed

+85
-5
lines changed

6 files changed

+85
-5
lines changed

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/AbstractStringBasedJpaQuery.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ public Query doCreateQuery(JpaParametersParameterAccessor accessor) {
145145
ReturnedType getReturnedType(ResultProcessor processor) {
146146

147147
ReturnedType returnedType = processor.getReturnedType();
148-
Class<?> returnedJavaType = processor.getReturnedType().getReturnedType();
148+
Class<?> returnedJavaType = returnedType.getReturnedType();
149149

150150
if (!returnedType.isProjecting() || returnedJavaType.isInterface() || query.isNativeQuery()) {
151151
return returnedType;
@@ -157,7 +157,8 @@ ReturnedType getReturnedType(ResultProcessor processor) {
157157
return returnedType;
158158
}
159159

160-
if ((known != null && !known) || returnedJavaType.isArray() || getMetamodel().isJpaManaged(returnedJavaType)) {
160+
if ((known != null && !known) || returnedJavaType.isArray() || getMetamodel().isJpaManaged(returnedJavaType)
161+
|| !returnedType.needsCustomConstruction()) {
161162
if (known == null) {
162163
knownProjections.put(returnedJavaType, false);
163164
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* Copyright 2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.jpa.domain.sample;
17+
18+
/**
19+
* @author Mark Paluch
20+
*/
21+
public class Country {
22+
23+
private final String code;
24+
25+
// workaround to avoid DTO projections as needsCustomConstruction is false.
26+
private Country(Country other) {
27+
this.code = other.code;
28+
}
29+
30+
private Country(String code) {
31+
this.code = code;
32+
}
33+
34+
public static Country of(String code) {
35+
return new Country(code);
36+
}
37+
38+
public String getCode() {
39+
return code;
40+
}
41+
}

spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/Customer.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
@Entity
2626
public class Customer {
2727

28-
@Id Long id;
28+
@Id Long id;
2929

30-
String name;
30+
String name;
3131
}

spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/RepositoryWithCompositeKeyTests.java

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
*/
1616
package org.springframework.data.jpa.repository;
1717

18-
import static org.assertj.core.api.Assertions.assertThat;
18+
import static org.assertj.core.api.Assertions.*;
1919

2020
import jakarta.persistence.EntityManager;
2121

@@ -25,6 +25,7 @@
2525

2626
import org.junit.jupiter.api.Test;
2727
import org.junit.jupiter.api.extension.ExtendWith;
28+
2829
import org.springframework.beans.factory.annotation.Autowired;
2930
import org.springframework.data.domain.Page;
3031
import org.springframework.data.domain.PageRequest;
@@ -115,6 +116,24 @@ void shouldSupportSavingEntitiesWithCompositeKeyClassesWithEmbeddedIdsAndDerived
115116
assertThat(persistedEmp.getDepartment().getName()).isEqualTo(dep.getName());
116117
}
117118

119+
@Test // GH-3929
120+
void shouldReturnIdentifiers() {
121+
122+
EmbeddedIdExampleDepartment dep = new EmbeddedIdExampleDepartment();
123+
dep.setName("TestDepartment");
124+
dep.setDepartmentId(-1L);
125+
126+
EmbeddedIdExampleEmployee emp = new EmbeddedIdExampleEmployee();
127+
emp.setDepartment(dep);
128+
emp.setEmployeePk(new EmbeddedIdExampleEmployeePK(1L, 2L));
129+
130+
emp = employeeRepositoryWithEmbeddedId.save(emp);
131+
132+
List<EmbeddedIdExampleEmployeePK> identifiers = employeeRepositoryWithEmbeddedId.findIdentifiers();
133+
134+
assertThat(identifiers).hasSize(1).contains(emp.getEmployeePk());
135+
}
136+
118137
@Test // DATAJPA-472, DATAJPA-912
119138
void shouldSupportFindAllWithPageableAndEntityWithIdClass() {
120139

spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/SimpleJpaQueryUnitTests.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
import org.springframework.data.domain.PageRequest;
4646
import org.springframework.data.domain.Pageable;
4747
import org.springframework.data.domain.Sort;
48+
import org.springframework.data.jpa.domain.sample.Country;
4849
import org.springframework.data.jpa.domain.sample.User;
4950
import org.springframework.data.jpa.provider.QueryExtractor;
5051
import org.springframework.data.jpa.repository.NativeQuery;
@@ -325,6 +326,17 @@ void jdbcStyleParametersOnlyAllowedInNativeQueries() throws Exception {
325326
assertThatIllegalArgumentException().isThrownBy(() -> createJpaQuery(illegalMethod));
326327
}
327328

329+
@Test // GH-3929
330+
void doesNotRewriteQueryForDtoWithMultipleConstructors() throws Exception {
331+
332+
AbstractStringBasedJpaQuery jpaQuery = (AbstractStringBasedJpaQuery) createJpaQuery(
333+
SampleRepository.class.getMethod("justCountries"));
334+
335+
String queryString = createQuery(jpaQuery);
336+
337+
assertThat(queryString).startsWith("select u.country from User u");
338+
}
339+
328340
@Test // DATAJPA-1163
329341
void resolvesExpressionInCountQuery() throws Exception {
330342

@@ -408,6 +420,9 @@ interface SampleRepository extends Repository<User, Long> {
408420
@Query("select r.name from User u LEFT JOIN FETCH u.roles r")
409421
Collection<UnrelatedType> projectWithJoinPaths();
410422

423+
@Query("select u.country from User u")
424+
Collection<Country> justCountries();
425+
411426
@Query(value = "select u from #{#entityName} u", countQuery = "select count(u.id) from #{#entityName} u")
412427
List<User> findAllWithExpressionInCountQuery(Pageable pageable);
413428

spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/EmployeeRepositoryWithEmbeddedId.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import org.springframework.data.jpa.domain.sample.EmbeddedIdExampleEmployee;
2222
import org.springframework.data.jpa.domain.sample.EmbeddedIdExampleEmployeePK;
2323
import org.springframework.data.jpa.repository.JpaRepository;
24+
import org.springframework.data.jpa.repository.Query;
2425
import org.springframework.data.querydsl.QuerydslPredicateExecutor;
2526

2627
import com.querydsl.core.types.OrderSpecifier;
@@ -40,6 +41,9 @@ public interface EmployeeRepositoryWithEmbeddedId
4041
@Override
4142
List<EmbeddedIdExampleEmployee> findAll(Predicate predicate, OrderSpecifier<?>... orders);
4243

44+
@Query("select e.employeePk from EmbeddedIdExampleEmployee e")
45+
List<EmbeddedIdExampleEmployeePK> findIdentifiers();
46+
4347
// DATAJPA-920
4448
boolean existsByName(String name);
4549
}

0 commit comments

Comments
 (0)