From 94055c77066c2f3a14003e95ac0d9917e612549d Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Fri, 14 Nov 2025 13:56:28 +0100 Subject: [PATCH 1/4] Prepare next development iteration. See #4051 --- pom.xml | 2 +- spring-data-envers/pom.xml | 4 ++-- spring-data-jpa-distribution/pom.xml | 2 +- spring-data-jpa/pom.xml | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pom.xml b/pom.xml index 0dc1d09b05..99f6cad51e 100755 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-jpa-parent - 4.0.0 + 4.0.1-SNAPSHOT pom Spring Data JPA Parent diff --git a/spring-data-envers/pom.xml b/spring-data-envers/pom.xml index 62e7e31461..b0c76f2722 100755 --- a/spring-data-envers/pom.xml +++ b/spring-data-envers/pom.xml @@ -5,12 +5,12 @@ org.springframework.data spring-data-envers - 4.0.0 + 4.0.1-SNAPSHOT org.springframework.data spring-data-jpa-parent - 4.0.0 + 4.0.1-SNAPSHOT ../pom.xml diff --git a/spring-data-jpa-distribution/pom.xml b/spring-data-jpa-distribution/pom.xml index 6ffeef0594..283893c043 100644 --- a/spring-data-jpa-distribution/pom.xml +++ b/spring-data-jpa-distribution/pom.xml @@ -14,7 +14,7 @@ org.springframework.data spring-data-jpa-parent - 4.0.0 + 4.0.1-SNAPSHOT ../pom.xml diff --git a/spring-data-jpa/pom.xml b/spring-data-jpa/pom.xml index a2c59aa87e..d5cf754246 100644 --- a/spring-data-jpa/pom.xml +++ b/spring-data-jpa/pom.xml @@ -7,7 +7,7 @@ org.springframework.data spring-data-jpa - 4.0.0 + 4.0.1-SNAPSHOT Spring Data JPA Spring Data module for JPA repositories. @@ -16,7 +16,7 @@ org.springframework.data spring-data-jpa-parent - 4.0.0 + 4.0.1-SNAPSHOT ../pom.xml From 014776d78959cc7f8e46df13a3df5f1a0e22cba2 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Fri, 14 Nov 2025 13:56:29 +0100 Subject: [PATCH 2/4] After release cleanups. See #4051 --- Jenkinsfile | 2 +- pom.xml | 20 ++++++++++++++++---- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 2a026b08fa..d67b8c76a1 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -9,7 +9,7 @@ pipeline { triggers { pollSCM 'H/10 * * * *' - upstream(upstreamProjects: "spring-data-commons/main", threshold: hudson.model.Result.SUCCESS) + upstream(upstreamProjects: "spring-data-commons/4.0.x", threshold: hudson.model.Result.SUCCESS) } options { diff --git a/pom.xml b/pom.xml index 99f6cad51e..8bbfd5c919 100755 --- a/pom.xml +++ b/pom.xml @@ -23,7 +23,7 @@ org.springframework.data.build spring-data-parent - 4.0.0 + 4.0.1-SNAPSHOT @@ -39,7 +39,7 @@ 9.5.0 42.7.8 23.26.0.0.0 - 4.0.0 + 4.0.1-SNAPSHOT 0.10.3 org.hibernate @@ -193,8 +193,20 @@ - - + + spring-snapshot + https://repo.spring.io/snapshot + + true + + + false + + + + spring-milestone + https://repo.spring.io/milestone + From bf8f72caad1386c47ce0f6df15bdb6267b27eec3 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Wed, 19 Nov 2025 09:36:38 +0100 Subject: [PATCH 3/4] Defer `ReturnedType.inputProperties` access. Closes #4088 --- .../repository/query/AbstractJpaQuery.java | 21 +++++++++++++++++-- .../query/AbstractStringBasedJpaQuery.java | 3 ++- .../DtoProjectionTransformerDelegate.java | 11 +++++----- .../query/EqlSortedQueryTransformer.java | 2 +- .../query/HqlSortedQueryTransformer.java | 2 +- .../query/JpqlSortedQueryTransformer.java | 2 +- 6 files changed, 30 insertions(+), 11 deletions(-) diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/AbstractJpaQuery.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/AbstractJpaQuery.java index ad0cafba95..851a14ea6b 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/AbstractJpaQuery.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/AbstractJpaQuery.java @@ -165,7 +165,7 @@ protected JpaMetamodel getMetamodel() { ResultProcessor withDynamicProjection = method.getResultProcessor().withDynamicProjection(accessor); return withDynamicProjection.processResult(result, - new TupleConverter(withDynamicProjection.getReturnedType(), method.isNativeQuery())); + new LazyTupleConverter(withDynamicProjection.getReturnedType(), method.isNativeQuery())); } private JpaParametersParameterAccessor obtainParameterAccessor(Object[] values) { @@ -325,6 +325,23 @@ protected Query createCountQuery(JpaParametersParameterAccessor values) { */ protected abstract Query doCreateCountQuery(JpaParametersParameterAccessor accessor); + /** + * Lazy variant of {@link TupleConverter} to avoid early instantiation. + */ + private static class LazyTupleConverter implements Converter { + + private final Lazy delegate; + + public LazyTupleConverter(ReturnedType type, boolean nativeQuery) { + this.delegate = Lazy.of(() -> new TupleConverter(type, nativeQuery)); + } + + @Override + public Object convert(Object source) { + return delegate.get().convert(source); + } + } + public static class TupleConverter implements Converter { private final ReturnedType type; @@ -358,7 +375,7 @@ public TupleConverter(ReturnedType type, boolean nativeQuery) { this.type = type; this.tupleWrapper = nativeQuery ? TupleBackedMap::underscoreAware : UnaryOperator.identity(); this.dtoProjection = type.isProjecting() && !type.getReturnedType().isInterface() - && !type.getInputProperties().isEmpty(); + && type.needsCustomConstruction(); if (this.dtoProjection) { this.preferredConstructor = PreferredConstructorDiscoverer.discover(type.getReturnedType()); diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/AbstractStringBasedJpaQuery.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/AbstractStringBasedJpaQuery.java index 09f54bbc7b..4b8879dc86 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/AbstractStringBasedJpaQuery.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/AbstractStringBasedJpaQuery.java @@ -161,7 +161,8 @@ ReturnedType getReturnedType(ResultProcessor processor) { ReturnedType returnedType = processor.getReturnedType(); Class returnedJavaType = returnedType.getReturnedType(); - if (!returnedType.isProjecting() || returnedJavaType.isInterface() || query.isNative()) { + if (query.hasConstructorExpression() || !returnedType.isProjecting() || returnedJavaType.isInterface() + || query.isNative()) { return returnedType; } 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 28de9ba657..70cc36813f 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 @@ -23,6 +23,7 @@ import java.util.function.Function; import org.springframework.data.repository.query.ReturnedType; +import org.springframework.data.util.Lazy; /** * HQL Query Transformer that rewrites the query using constructor expressions. @@ -40,21 +41,21 @@ class DtoProjectionTransformerDelegate { private final ReturnedType returnedType; - private final boolean applyRewriting; + private final Lazy applyRewriting; private final List selectItems = new ArrayList<>(); public DtoProjectionTransformerDelegate(ReturnedType returnedType) { this.returnedType = returnedType; - this.applyRewriting = returnedType.isProjecting() && !returnedType.getReturnedType().isInterface() - && returnedType.needsCustomConstruction(); + this.applyRewriting = Lazy.of(() -> returnedType.isProjecting() && !returnedType.getReturnedType().isInterface() + && returnedType.needsCustomConstruction()); } public boolean applyRewriting() { - return applyRewriting; + return applyRewriting.get(); } public boolean canRewrite() { - return applyRewriting() && !selectItems.isEmpty(); + return !selectItems.isEmpty() && applyRewriting(); } public void appendSelectItem(QueryTokenStream selectItem) { diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlSortedQueryTransformer.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlSortedQueryTransformer.java index 2cb03ae4df..8963607052 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlSortedQueryTransformer.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlSortedQueryTransformer.java @@ -147,7 +147,7 @@ public QueryTokenStream visitSelect_expression(EqlParser.Select_expressionContex QueryTokenStream selectItem = super.visitSelect_expression(ctx); - if (dtoDelegate != null && dtoDelegate.applyRewriting() && ctx.constructor_expression() == null) { + if (ctx.constructor_expression() == null && dtoDelegate != null && dtoDelegate.applyRewriting()) { dtoDelegate.appendSelectItem(selectItem); } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlSortedQueryTransformer.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlSortedQueryTransformer.java index 46a22d1ecb..8fe7c7f96f 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlSortedQueryTransformer.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlSortedQueryTransformer.java @@ -105,7 +105,7 @@ public QueryTokenStream visitSelectExpression(HqlParser.SelectExpressionContext QueryTokenStream selectItem = super.visitSelectExpression(ctx); - if (dtoDelegate != null && dtoDelegate.applyRewriting() && ctx.instantiation() == null && !isSubquery(ctx)) { + if (ctx.instantiation() == null && !isSubquery(ctx) && dtoDelegate != null && dtoDelegate.applyRewriting()) { dtoDelegate.appendSelectItem(QueryRenderer.ofExpression(selectItem)); } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlSortedQueryTransformer.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlSortedQueryTransformer.java index 9a86eb4424..59505f2c06 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlSortedQueryTransformer.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlSortedQueryTransformer.java @@ -146,7 +146,7 @@ public QueryTokenStream visitSelect_expression(JpqlParser.Select_expressionConte QueryTokenStream selectItem = super.visitSelect_expression(ctx); - if (dtoDelegate != null && dtoDelegate.applyRewriting() && ctx.constructor_expression() == null) { + if (ctx.constructor_expression() == null && dtoDelegate != null && dtoDelegate.applyRewriting()) { dtoDelegate.appendSelectItem(selectItem); } From 0999b95fcb27a227fa134c12c9187ca447893ace Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Wed, 19 Nov 2025 10:06:22 +0100 Subject: [PATCH 4/4] Polishing. Use ReturnedType.isDtoProjection/isInterfaceProjection methods. See #4088 --- .../data/jpa/repository/aot/QueriesFactory.java | 5 ++--- .../jpa/repository/query/AbstractJpaQuery.java | 4 ++-- .../query/DtoProjectionTransformerDelegate.java | 2 +- .../data/jpa/repository/query/JpaQueryCreator.java | 2 +- .../data/jpa/repository/query/NativeJpaQuery.java | 2 +- .../support/FetchableFluentQueryByPredicate.java | 7 ++++--- .../repository/support/SimpleJpaRepository.java | 2 +- .../repository/query/TupleConverterUnitTests.java | 14 +++++++++----- 8 files changed, 21 insertions(+), 17 deletions(-) diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/aot/QueriesFactory.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/aot/QueriesFactory.java index 83a1d3dce7..e577103d01 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/aot/QueriesFactory.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/aot/QueriesFactory.java @@ -154,8 +154,7 @@ private AotQueries buildStringQuery(ReturnedType returnedType, QueryEnhancerSele StringAotQuery aotStringQuery = StringAotQuery.of(entityQuery); String countQuery = query.getString("countQuery"); - if (returnedType.isProjecting() && returnedType.hasInputProperties() - && !returnedType.getReturnedType().isInterface()) { + if (returnedType.isDtoProjection() && returnedType.hasInputProperties()) { QueryProvider rewritten = entityQuery.rewrite(new QueryEnhancer.QueryRewriteInformation() { @Override @@ -328,7 +327,7 @@ private AotQuery createCountQuery(PartTree partTree, ReturnedType returnedType, if (returnedType.isProjecting()) { - if (returnedType.getReturnedType().isInterface()) { + if (returnedType.isInterfaceProjection()) { if (query.hasConstructorExpressionOrDefaultProjection()) { return result; diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/AbstractJpaQuery.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/AbstractJpaQuery.java index 851a14ea6b..08f0884d47 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/AbstractJpaQuery.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/AbstractJpaQuery.java @@ -303,7 +303,7 @@ protected Query createCountQuery(JpaParametersParameterAccessor values) { return null; } - return returnedType.isProjecting() && returnedType.getReturnedType().isInterface() + return returnedType.isInterfaceProjection() && !getMetamodel().isJpaManaged(returnedType.getReturnedType()) // ? Tuple.class // : null; @@ -374,7 +374,7 @@ public TupleConverter(ReturnedType type, boolean nativeQuery) { this.type = type; this.tupleWrapper = nativeQuery ? TupleBackedMap::underscoreAware : UnaryOperator.identity(); - this.dtoProjection = type.isProjecting() && !type.getReturnedType().isInterface() + this.dtoProjection = type.isDtoProjection() && type.needsCustomConstruction(); if (this.dtoProjection) { 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 70cc36813f..ea4a0848e4 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 @@ -46,7 +46,7 @@ class DtoProjectionTransformerDelegate { public DtoProjectionTransformerDelegate(ReturnedType returnedType) { this.returnedType = returnedType; - this.applyRewriting = Lazy.of(() -> returnedType.isProjecting() && !returnedType.getReturnedType().isInterface() + this.applyRewriting = Lazy.of(() -> returnedType.isDtoProjection() && returnedType.needsCustomConstruction()); } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryCreator.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryCreator.java index e3227788c8..039b2e85dc 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryCreator.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryCreator.java @@ -165,7 +165,7 @@ JpqlQueryBuilder.Entity getEntity() { } public boolean useTupleQuery() { - return returnedType.needsCustomConstruction() && returnedType.getReturnedType().isInterface(); + return returnedType.needsCustomConstruction() && returnedType.isInterfaceProjection(); } /** diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/NativeJpaQuery.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/NativeJpaQuery.java index 35045c5e25..d1bcff1814 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/NativeJpaQuery.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/NativeJpaQuery.java @@ -114,7 +114,7 @@ protected Query createJpaQuery(QueryProvider declaredQuery, Sort sort, @Nullable if (returnedType.isProjecting()) { - if (returnedType.getReturnedType().isInterface()) { + if (returnedType.isInterfaceProjection()) { return Tuple.class; } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/FetchableFluentQueryByPredicate.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/FetchableFluentQueryByPredicate.java index 171e59012e..a0c81e2cd2 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/FetchableFluentQueryByPredicate.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/FetchableFluentQueryByPredicate.java @@ -25,6 +25,8 @@ import java.util.function.Function; import java.util.stream.Stream; +import org.jspecify.annotations.Nullable; + import org.springframework.dao.IncorrectResultSizeDataAccessException; import org.springframework.data.domain.KeysetScrollPosition; import org.springframework.data.domain.Page; @@ -51,7 +53,6 @@ import com.querydsl.core.types.dsl.PathBuilder; import com.querydsl.jpa.JPQLSerializer; import com.querydsl.jpa.impl.AbstractJPAQuery; -import org.jspecify.annotations.Nullable; /** * Immutable implementation of {@link FetchableFluentQuery} based on a Querydsl {@link Predicate}. All methods that @@ -231,7 +232,7 @@ private void applyQuerySettings(ReturnedType returnedType, int limit, AbstractJP if (returnedType.needsCustomConstruction()) { Collection requiredSelection; - if (scrollPosition instanceof KeysetScrollPosition && returnedType.getReturnedType().isInterface()) { + if (scrollPosition instanceof KeysetScrollPosition && returnedType.isInterfaceProjection()) { requiredSelection = KeysetScrollDelegate.getProjectionInputProperties(entityInformation, inputProperties, sort); } else { requiredSelection = inputProperties; @@ -240,7 +241,7 @@ private void applyQuerySettings(ReturnedType returnedType, int limit, AbstractJP PathBuilder builder = new PathBuilder<>(entityPath.getType(), entityPath.getMetadata()); Expression[] projection = requiredSelection.stream().map(builder::get).toArray(Expression[]::new); - if (returnedType.getReturnedType().isInterface()) { + if (returnedType.isInterfaceProjection()) { query.select(new JakartaTuple(projection)); } else { query.select(new DtoProjection(returnedType.getReturnedType(), projection)); diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java index 39e59a7a23..6324bd44b4 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java @@ -807,7 +807,7 @@ private TypedQuery getQuery(ReturnedType returnedType, @Nullabl CriteriaBuilder builder = entityManager.getCriteriaBuilder(); CriteriaQuery query; - boolean interfaceProjection = returnedType.getReturnedType().isInterface(); + boolean interfaceProjection = returnedType.isInterfaceProjection(); if (returnedType.needsCustomConstruction() && (inputProperties.isEmpty() || !interfaceProjection)) { inputProperties = returnedType.getInputProperties(); diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/TupleConverterUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/TupleConverterUnitTests.java index 3321c2584d..65ffd8b055 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/TupleConverterUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/TupleConverterUnitTests.java @@ -73,7 +73,6 @@ void setUp() throws Exception { } @Test // DATAJPA-984 - @SuppressWarnings("unchecked") void returnsSingleTupleElementIfItMatchesExpectedType() { doReturn(Collections.singletonList(element)).when(tuple).getElements(); @@ -85,7 +84,6 @@ void returnsSingleTupleElementIfItMatchesExpectedType() { } @Test // DATAJPA-1024 - @SuppressWarnings("unchecked") void returnsNullForSingleElementTupleWithNullValue() { doReturn(Collections.singletonList(element)).when(tuple).getElements(); @@ -130,13 +128,15 @@ void dealsWithNullsInArguments() { assertThat(result).isInstanceOf(WithPC.class); } - @Test // GH-3076 + @Test // GH-3076, GH-4088 void fallsBackToCompatibleConstructor() { ReturnedType returnedType = spy( ReturnedType.of(MultipleConstructors.class, DomainType.class, new SpelAwareProxyProjectionFactory())); when(returnedType.isProjecting()).thenReturn(true); + when(returnedType.isDtoProjection()).thenReturn(true); when(returnedType.getInputProperties()).thenReturn(Arrays.asList("one", "two", "three")); + when(returnedType.hasInputProperties()).thenReturn(true); doReturn(List.of(element, element, element)).when(tuple).getElements(); when(tuple.get(eq(0))).thenReturn("one"); @@ -163,13 +163,15 @@ void fallsBackToCompatibleConstructor() { assertThat(result.three).isEqualTo(97); } - @Test // GH-3076 + @Test // GH-3076, GH-4088 void acceptsConstructorWithCastableType() { ReturnedType returnedType = spy( ReturnedType.of(MultipleConstructors.class, DomainType.class, new SpelAwareProxyProjectionFactory())); when(returnedType.isProjecting()).thenReturn(true); + when(returnedType.isDtoProjection()).thenReturn(true); when(returnedType.getInputProperties()).thenReturn(Arrays.asList("one", "two", "three", "four")); + when(returnedType.hasInputProperties()).thenReturn(true); doReturn(List.of(element, element, element, element)).when(tuple).getElements(); when(tuple.get(eq(0))).thenReturn("one"); @@ -185,13 +187,15 @@ void acceptsConstructorWithCastableType() { assertThat(result.four).isEqualTo(2, offset(0.1d)); } - @Test // GH-3076 + @Test // GH-3076, GH-4088 void failsForNonResolvableConstructor() { ReturnedType returnedType = spy( ReturnedType.of(MultipleConstructors.class, DomainType.class, new SpelAwareProxyProjectionFactory())); when(returnedType.isProjecting()).thenReturn(true); + when(returnedType.isDtoProjection()).thenReturn(true); when(returnedType.getInputProperties()).thenReturn(Arrays.asList("one", "two")); + when(returnedType.hasInputProperties()).thenReturn(true); doReturn(List.of(element, element)).when(tuple).getElements(); when(tuple.get(eq(0))).thenReturn(1);