From 828c7e2ae4e99d86e970178edd1e4dfffe9fc51f Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Fri, 7 Nov 2025 11:09:14 +0100 Subject: [PATCH] Polishing. Move Streamable check to QueryBlocks, move off deprecated API. See #5089 Original pull request: #5090 --- .../repository/aot/AggregationBlocks.java | 11 ++--- .../mongodb/repository/aot/DeleteBlocks.java | 12 +++--- .../mongodb/repository/aot/GeoBlocks.java | 6 ++- .../repository/aot/MongoCodeBlocks.java | 40 ++++++++----------- .../mongodb/repository/aot/QueryBlocks.java | 20 +++++----- .../repository/aot/VectorSearchBlocks.java | 4 +- .../DbRefMappingMongoConverterUnitTests.java | 11 +++-- .../core/query/BasicQueryUnitTests.java | 5 +-- ...RepositoryLazyLoadingIntegrationTests.java | 11 +++-- 9 files changed, 52 insertions(+), 68 deletions(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/AggregationBlocks.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/AggregationBlocks.java index 56ca6db4e..37f4fdf8b 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/AggregationBlocks.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/AggregationBlocks.java @@ -41,7 +41,6 @@ import org.springframework.data.mongodb.repository.ReadPreference; import org.springframework.data.mongodb.repository.query.MongoQueryMethod; import org.springframework.data.repository.aot.generate.AotQueryMethodGenerationContext; import org.springframework.data.util.ReflectionUtils; -import org.springframework.data.util.Streamable; import org.springframework.javapoet.CodeBlock; import org.springframework.javapoet.CodeBlock.Builder; import org.springframework.util.ClassUtils; @@ -146,15 +145,11 @@ class AggregationBlocks { builder.addStatement("return $L.aggregateStream($L, $T.class)", mongoOpsRef, aggregationVariableName, outputType); } else { - - CodeBlock resultBlock = CodeBlock.of("$L.aggregate($L, $T.class).getMappedResults()", mongoOpsRef, + CodeBlock codeBlock = CodeBlock.of("$L.aggregate($L, $T.class).getMappedResults()", mongoOpsRef, aggregationVariableName, outputType); - if (queryMethod.getReturnType().getType().equals(Streamable.class)) { - resultBlock = CodeBlock.of("$T.of($L)", Streamable.class, resultBlock); - } - - builder.addStatement("return $L", resultBlock); + builder.addStatement("return $L", + MongoCodeBlocks.potentiallyWrapStreamable(context.getMethodReturn(), codeBlock)); } } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/DeleteBlocks.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/DeleteBlocks.java index 74f11a1ae..5853dcd47 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/DeleteBlocks.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/DeleteBlocks.java @@ -18,17 +18,18 @@ package org.springframework.data.mongodb.repository.aot; import java.util.Optional; import org.jspecify.annotations.NullUnmarked; + import org.springframework.core.ResolvableType; import org.springframework.data.mongodb.core.ExecutableRemoveOperation.ExecutableRemove; import org.springframework.data.mongodb.core.MongoOperations; import org.springframework.data.mongodb.repository.query.MongoQueryExecution.DeleteExecution; import org.springframework.data.mongodb.repository.query.MongoQueryMethod; import org.springframework.data.repository.aot.generate.AotQueryMethodGenerationContext; +import org.springframework.data.repository.aot.generate.MethodReturn; import org.springframework.javapoet.CodeBlock; import org.springframework.javapoet.CodeBlock.Builder; import org.springframework.javapoet.TypeName; import org.springframework.util.ClassUtils; -import org.springframework.util.ObjectUtils; /** * @author Christoph Strobl @@ -59,12 +60,11 @@ class DeleteBlocks { String mongoOpsRef = context.fieldNameOf(MongoOperations.class); Builder builder = CodeBlock.builder(); - + MethodReturn methodReturn = context.getMethodReturn(); Class domainType = context.getRepositoryInformation().getDomainType(); - boolean isProjecting = context.getActualReturnType() != null - && !ObjectUtils.nullSafeEquals(TypeName.get(domainType), context.getActualReturnType()); + boolean isProjecting = methodReturn.isProjecting(); - Object actualReturnType = isProjecting ? context.getActualReturnType().getType() : domainType; + Object actualReturnType = isProjecting ? methodReturn.getActualTypeName() : domainType; builder.add("\n"); VariableSnippet remover = Snippet.declare(builder) @@ -83,7 +83,7 @@ class DeleteBlocks { actualReturnType = ClassUtils.isPrimitiveOrWrapper(context.getMethod().getReturnType()) ? TypeName.get(context.getMethod().getReturnType()) - : queryMethod.isCollectionQuery() ? context.getReturnTypeName() : actualReturnType; + : queryMethod.isCollectionQuery() ? methodReturn.getTypeName() : actualReturnType; if (ClassUtils.isVoidType(context.getMethod().getReturnType())) { builder.addStatement("new $T($L, $T.$L).execute($L)", DeleteExecution.class, remover.getVariableName(), diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/GeoBlocks.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/GeoBlocks.java index 2e8e4a0a0..35950ea80 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/GeoBlocks.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/GeoBlocks.java @@ -24,6 +24,7 @@ import org.springframework.data.mongodb.core.MongoOperations; import org.springframework.data.mongodb.core.query.NearQuery; import org.springframework.data.mongodb.repository.query.MongoQueryMethod; import org.springframework.data.repository.aot.generate.AotQueryMethodGenerationContext; +import org.springframework.data.repository.aot.generate.MethodReturn; import org.springframework.data.support.PageableExecutionUtils; import org.springframework.javapoet.CodeBlock; import org.springframework.util.ClassUtils; @@ -117,12 +118,13 @@ class GeoBlocks { CodeBlock.Builder builder = CodeBlock.builder(); builder.add("\n"); + MethodReturn methodReturn = context.getMethodReturn(); VariableSnippet queryExecutor = Snippet.declare(builder).variable(context.localVariable("nearFinder")).as( "$L.query($T.class).near($L)", context.fieldNameOf(MongoOperations.class), context.getRepositoryInformation().getDomainType(), queryVariableName); - if (ClassUtils.isAssignable(GeoPage.class, context.getReturnType().getRawClass())) { + if (ClassUtils.isAssignable(GeoPage.class, methodReturn.toClass())) { VariableSnippet geoResult = Snippet.declare(builder).variable(context.localVariable("geoResult")).as("$L.all()", queryExecutor.getVariableName()); @@ -137,7 +139,7 @@ class GeoBlocks { builder.addStatement("return new $T<>($L, $L, $L.getTotalElements())", GeoPage.class, geoResult.getVariableName(), context.getPageableParameterName(), resultPage.getVariableName()); - } else if (ClassUtils.isAssignable(GeoResults.class, context.getReturnType().getRawClass())) { + } else if (ClassUtils.isAssignable(GeoResults.class, methodReturn.toClass())) { builder.addStatement("return $L.all()", queryExecutor.getVariableName()); } else { builder.addStatement("return $L.all().getContent()", queryExecutor.getVariableName()); diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/MongoCodeBlocks.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/MongoCodeBlocks.java index 5fd577e1f..841fe1c2f 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/MongoCodeBlocks.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/MongoCodeBlocks.java @@ -34,6 +34,8 @@ import org.springframework.data.mongodb.repository.aot.UpdateBlocks.UpdateExecut import org.springframework.data.mongodb.repository.query.MongoQueryMethod; import org.springframework.data.repository.aot.generate.AotQueryMethodGenerationContext; import org.springframework.data.repository.aot.generate.ExpressionMarker; +import org.springframework.data.repository.aot.generate.MethodReturn; +import org.springframework.data.util.Streamable; import org.springframework.javapoet.CodeBlock; import org.springframework.javapoet.CodeBlock.Builder; import org.springframework.util.NumberUtils; @@ -98,7 +100,6 @@ class MongoCodeBlocks { * @return */ static UpdateCodeBlockBuilder updateBlockBuilder(AotQueryMethodGenerationContext context) { - return new UpdateCodeBlockBuilder(context); } @@ -111,44 +112,27 @@ class MongoCodeBlocks { */ static UpdateExecutionCodeBlockBuilder updateExecutionBlockBuilder(AotQueryMethodGenerationContext context, MongoQueryMethod queryMethod) { - return new UpdateExecutionCodeBlockBuilder(context, queryMethod); } /** * Builder for generating aggregation (pipeline) parsing {@link CodeBlock}. - * - * @param context - * @param simpleTypeHolder - * @param queryMethod - * @return */ static AggregationCodeBlockBuilder aggregationBlockBuilder(AotQueryMethodGenerationContext context, - SimpleTypeHolder simpleTypeHolder, - MongoQueryMethod queryMethod) { + SimpleTypeHolder simpleTypeHolder, MongoQueryMethod queryMethod) { return new AggregationCodeBlockBuilder(context, simpleTypeHolder, queryMethod); } /** * Builder for generating aggregation execution {@link CodeBlock}. - * - * @param context - * @param simpleTypeHolder - * @param queryMethod - * @return */ static AggregationExecutionCodeBlockBuilder aggregationExecutionBlockBuilder(AotQueryMethodGenerationContext context, - SimpleTypeHolder simpleTypeHolder, - MongoQueryMethod queryMethod) { + SimpleTypeHolder simpleTypeHolder, MongoQueryMethod queryMethod) { return new AggregationExecutionCodeBlockBuilder(context, simpleTypeHolder, queryMethod); } /** * Builder for generating {@link org.springframework.data.mongodb.core.query.NearQuery} {@link CodeBlock}. - * - * @param context - * @param queryMethod - * @return */ static GeoNearCodeBlockBuilder geoNearBlockBuilder(AotQueryMethodGenerationContext context, MongoQueryMethod queryMethod) { @@ -159,12 +143,8 @@ class MongoCodeBlocks { /** * Builder for generating {@link org.springframework.data.mongodb.core.query.NearQuery} execution {@link CodeBlock} * that can return {@link org.springframework.data.geo.GeoResults}. - * - * @param context - * @return */ static GeoNearExecutionCodeBlockBuilder geoNearExecutionBlockBuilder(AotQueryMethodGenerationContext context) { - return new GeoNearExecutionCodeBlockBuilder(context); } @@ -205,6 +185,7 @@ class MongoCodeBlocks { static CodeBlock evaluateNumberPotentially(String value, Class targetType, AotQueryMethodGenerationContext context) { + try { Number number = NumberUtils.parseNumber(value, targetType); return CodeBlock.of("$L", number); @@ -251,4 +232,15 @@ class MongoCodeBlocks { readPreference); } } + + /** + * Wraps the given {@link CodeBlock} representing an {@link Iterable} into a {@link Streamable} if the + * {@link MethodReturn} indicates so. + */ + public static CodeBlock potentiallyWrapStreamable(MethodReturn methodReturn, CodeBlock returningIterable) { + return methodReturn.toClass().equals(Streamable.class) + ? CodeBlock.of("$T.of($L)", Streamable.class, returningIterable) + : returningIterable; + } + } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/QueryBlocks.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/QueryBlocks.java index 2ed605c02..f6c9d0757 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/QueryBlocks.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/QueryBlocks.java @@ -20,6 +20,7 @@ import java.util.Optional; import org.bson.Document; import org.jspecify.annotations.NullUnmarked; + import org.springframework.core.annotation.MergedAnnotation; import org.springframework.data.domain.ScrollPosition; import org.springframework.data.mongodb.core.ExecutableFindOperation.FindWithQuery; @@ -33,11 +34,10 @@ import org.springframework.data.mongodb.repository.query.MongoQueryExecution.Pag import org.springframework.data.mongodb.repository.query.MongoQueryExecution.SlicedExecution; import org.springframework.data.mongodb.repository.query.MongoQueryMethod; import org.springframework.data.repository.aot.generate.AotQueryMethodGenerationContext; +import org.springframework.data.repository.aot.generate.MethodReturn; import org.springframework.data.util.Lazy; -import org.springframework.data.util.Streamable; import org.springframework.javapoet.CodeBlock; import org.springframework.javapoet.CodeBlock.Builder; -import org.springframework.javapoet.TypeName; import org.springframework.util.ClassUtils; import org.springframework.util.NumberUtils; import org.springframework.util.StringUtils; @@ -69,6 +69,7 @@ class QueryBlocks { CodeBlock build() { + MethodReturn methodReturn = context.getMethodReturn(); String mongoOpsRef = context.fieldNameOf(MongoOperations.class); Builder builder = CodeBlock.builder(); @@ -76,7 +77,7 @@ class QueryBlocks { boolean isProjecting = context.getReturnedType().isProjecting(); Class domainType = context.getRepositoryInformation().getDomainType(); Object actualReturnType = queryMethod.getParameters().hasDynamicProjection() || isProjecting - ? TypeName.get(context.getActualReturnType().getType()) + ? methodReturn.getActualTypeName() : domainType; builder.add("\n"); @@ -105,10 +106,10 @@ class QueryBlocks { terminatingMethod = "stream()"; } else { if (query.getQuery().isLimited()) { - terminatingMethod = Optional.class.isAssignableFrom(context.getReturnType().toClass()) ? "first()" + terminatingMethod = Optional.class.isAssignableFrom(methodReturn.toClass()) ? "first()" : "firstValue()"; } else { - terminatingMethod = Optional.class.isAssignableFrom(context.getReturnType().toClass()) ? "one()" + terminatingMethod = Optional.class.isAssignableFrom(methodReturn.toClass()) ? "one()" : "oneValue()"; } } @@ -139,7 +140,8 @@ class QueryBlocks { } } } else { - if (query.isCount() && !ClassUtils.isAssignable(Long.class, context.getActualReturnType().getRawClass())) { + + if (query.isCount() && !ClassUtils.isAssignable(Long.class, methodReturn.getActualReturnClass())) { Class returnType = ClassUtils.resolvePrimitiveIfNecessary(queryMethod.getReturnedObjectType()); builder.addStatement("return $T.convertNumberToTargetClass($L.matching($L).$L, $T.class)", NumberUtils.class, @@ -150,11 +152,7 @@ class QueryBlocks { CodeBlock resultBlock = CodeBlock.of("$L.matching($L).$L", context.localVariable("finder"), query.name(), terminatingMethod); - if (queryMethod.getReturnType().getType().equals(Streamable.class)) { - resultBlock = CodeBlock.of("$T.of($L)", Streamable.class, resultBlock); - } - - builder.addStatement("return $L", resultBlock); + builder.addStatement("return $L", MongoCodeBlocks.potentiallyWrapStreamable(methodReturn, resultBlock)); } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/VectorSearchBlocks.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/VectorSearchBlocks.java index 01eefb668..caefc1060 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/VectorSearchBlocks.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/VectorSearchBlocks.java @@ -166,10 +166,10 @@ class VectorSearchBlocks { builder.indent(); builder.add("$1T $4L = $5L.getMappedObject(parse($2S), $3T.class);\n", Document.class, filter.getSortString(), - context.getActualReturnType().getType(), mappedSort, ctx); + context.getMethodReturn().getActualClassName(), mappedSort, ctx); builder.add("return new $1T($2S, $3L.append(\"__score__\", -1));\n", Document.class, "$sort", mappedSort); builder.unindent(); - builder.add("};"); + builder.add("}"); return new ExpressionSnippet(builder.build()); } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DbRefMappingMongoConverterUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DbRefMappingMongoConverterUnitTests.java index 51ab70021..2fe357c12 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DbRefMappingMongoConverterUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DbRefMappingMongoConverterUnitTests.java @@ -36,9 +36,8 @@ import org.bson.conversions.Bson; import org.bson.types.ObjectId; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.DisabledForJreRange; -import org.junit.jupiter.api.condition.JRE; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.Mockito; @@ -186,7 +185,7 @@ class DbRefMappingMongoConverterUnitTests { } @Test // DATAMONGO-348 - @DisabledForJreRange(min = JRE.JAVA_16, disabledReason = "Class Proxies for eg; ArrayList require to open java.util.") + @Disabled("Class Proxies for eg; ArrayList require to open java.util.") void lazyLoadingProxyForLazyDbRefOnConcreteCollection() { String id = "42"; @@ -514,7 +513,7 @@ class DbRefMappingMongoConverterUnitTests { } @Test // DATAMONGO-1076 - @DisabledForJreRange(min = JRE.JAVA_16, disabledReason = "Class Proxies for eg; ArrayList require to open java.util.") + @Disabled("Class Proxies for eg; ArrayList require to open java.util.") void shouldNotTriggerResolvingOfLazyLoadedProxyWhenFinalizeMethodIsInvoked() throws Exception { MongoPersistentEntity entity = mappingContext @@ -533,7 +532,7 @@ class DbRefMappingMongoConverterUnitTests { } @Test // DATAMONGO-1194 - @DisabledForJreRange(min = JRE.JAVA_16, disabledReason = "Class Proxies for eg; ArrayList require to open java.util.") + @Disabled("Class Proxies for eg; ArrayList require to open java.util.") void shouldBulkFetchListOfReferences() { String id1 = "1"; @@ -584,7 +583,7 @@ class DbRefMappingMongoConverterUnitTests { } @Test // DATAMONGO-1194 - @DisabledForJreRange(min = JRE.JAVA_16, disabledReason = "Class Proxies for eg; ArrayList require to open java.util.") + @Disabled("Class Proxies for eg; ArrayList require to open java.util.") void shouldFallbackToOneByOneFetchingWhenElementsInListOfReferencesPointToDifferentCollections() { String id1 = "1"; diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/BasicQueryUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/BasicQueryUnitTests.java index 70bd6dc3d..300c4baf0 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/BasicQueryUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/query/BasicQueryUnitTests.java @@ -22,10 +22,9 @@ import nl.jqno.equalsverifier.EqualsVerifier; import nl.jqno.equalsverifier.Warning; import org.bson.Document; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.DisabledForJreRange; -import org.junit.jupiter.api.condition.JRE; import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort.Direction; @@ -66,7 +65,7 @@ public class BasicQueryUnitTests { } @Test // DATAMONGO-1093 - @DisabledForJreRange(min = JRE.JAVA_16, disabledReason = "EqualsVerifier uses reflection on Optional") + @Disabled("EqualsVerifier uses reflection on Optional") public void equalsContract() { BasicQuery query1 = new BasicQuery("{ \"name\" : \"Thomas\"}", "{\"name\":1, \"age\":1}"); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepositoryLazyLoadingIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepositoryLazyLoadingIntegrationTests.java index f985d8e37..0942a2c17 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepositoryLazyLoadingIntegrationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepositoryLazyLoadingIntegrationTests.java @@ -15,19 +15,18 @@ */ package org.springframework.data.mongodb.repository; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatNoException; -import static org.springframework.data.mongodb.core.convert.LazyLoadingTestUtils.assertProxyIsResolved; +import static org.assertj.core.api.Assertions.*; +import static org.springframework.data.mongodb.core.convert.LazyLoadingTestUtils.*; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.DisabledForJreRange; -import org.junit.jupiter.api.condition.JRE; import org.junit.jupiter.api.extension.ExtendWith; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.mongodb.core.MongoOperations; import org.springframework.test.context.ContextConfiguration; @@ -78,7 +77,7 @@ public class PersonRepositoryLazyLoadingIntegrationTests { } @Test // DATAMONGO-348 - @DisabledForJreRange(min = JRE.JAVA_16, disabledReason = "Class Proxies for eg; ArrayList require to open java.util.") + @Disabled("Class Proxies for eg; ArrayList require to open java.util.") public void shouldLoadAssociationWithDbRefOnConcreteCollectionAndLazyLoadingEnabled() { User thomas = new User();