diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableRemoveOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableRemoveOperation.java index de591a0fa..c29a448f1 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableRemoveOperation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableRemoveOperation.java @@ -55,6 +55,10 @@ public interface ExecutableRemoveOperation { */ ExecutableRemove remove(Class domainType); + /** + * @author Christoph Strobl + * @since 5.0 + */ interface TerminatingResults { /** diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableRemoveOperationSupport.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableRemoveOperationSupport.java index 77cd924e5..7817a7c8a 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableRemoveOperationSupport.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableRemoveOperationSupport.java @@ -109,6 +109,7 @@ class ExecutableRemoveOperationSupport implements ExecutableRemoveOperation { } @Override + @SuppressWarnings({"unchecked", "rawtypes"}) public TerminatingResults map(QueryResultConverter converter) { return new ExecutableRemoveSupport<>(template, (Class) domainType, query, collection, converter); } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java index eb372c6b9..663264f32 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java @@ -47,7 +47,6 @@ import org.bson.types.ObjectId; import org.jspecify.annotations.Nullable; import org.reactivestreams.Publisher; import org.reactivestreams.Subscriber; - import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; @@ -114,6 +113,7 @@ import org.springframework.data.mongodb.core.mapping.event.*; import org.springframework.data.mongodb.core.mapreduce.MapReduceOptions; import org.springframework.data.mongodb.core.query.BasicQuery; import org.springframework.data.mongodb.core.query.Collation; +import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.data.mongodb.core.query.Meta; import org.springframework.data.mongodb.core.query.NearQuery; import org.springframework.data.mongodb.core.query.Query; @@ -1137,9 +1137,8 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati return findAndModify(query, update, options, entityClass, getCollectionName(entityClass)); } - @Override - public Mono findAndModify(Query query, UpdateDefinition update, FindAndModifyOptions options, - Class entityClass, String collectionName) { + public Mono findAndModify(Query query, UpdateDefinition update, FindAndModifyOptions options, + Class entityClass, String collectionName, QueryResultConverter resultConverter) { Assert.notNull(options, "Options must not be null "); Assert.notNull(entityClass, "Entity class must not be null"); @@ -1156,13 +1155,27 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati } return doFindAndModify(collectionName, ReactiveCollectionPreparerDelegate.of(query), query.getQueryObject(), - query.getFieldsObject(), getMappedSortObject(query, entityClass), entityClass, update, optionsToUse); + query.getFieldsObject(), getMappedSortObject(query, entityClass), entityClass, update, optionsToUse, + resultConverter); + } + + @Override + public Mono findAndModify(Query query, UpdateDefinition update, FindAndModifyOptions options, + Class entityClass, String collectionName) { + return findAndModify(query, update, options, entityClass, collectionName, QueryResultConverter.entity()); } @Override - @SuppressWarnings("NullAway") public Mono findAndReplace(Query query, S replacement, FindAndReplaceOptions options, Class entityType, String collectionName, Class resultType) { + return findAndReplace(query, replacement, options, entityType, collectionName, resultType, + QueryResultConverter.entity()); + } + + @SuppressWarnings("NullAway") + public Mono findAndReplace(Query query, S replacement, FindAndReplaceOptions options, + Class entityType, String collectionName, Class resultType, + QueryResultConverter resultConverter) { Assert.notNull(query, "Query must not be null"); Assert.notNull(replacement, "Replacement must not be null"); @@ -1199,9 +1212,9 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati mapped.getCollection())); }).flatMap(it -> { - Mono afterFindAndReplace = doFindAndReplace(it.getCollection(), collectionPreparer, mappedQuery, + Mono afterFindAndReplace = doFindAndReplace(it.getCollection(), collectionPreparer, mappedQuery, mappedFields, mappedSort, queryContext.getCollation(entityType).orElse(null), entityType, it.getTarget(), - options, projection); + options, projection, resultConverter); return afterFindAndReplace.flatMap(saved -> { maybeEmitEvent(new AfterSaveEvent<>(saved, it.getTarget(), it.getCollection())); return maybeCallAfterSave(saved, it.getTarget(), it.getCollection()); @@ -2280,6 +2293,43 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati .flatMapSequential(deleteResult -> Flux.fromIterable(list))); } + Flux doFindAndDelete(String collectionName, Query query, Class entityClass, + QueryResultConverter resultConverter) { + + List ids = new ArrayList<>(); + ProjectingReadCallback readCallback = new ProjectingReadCallback(getConverter(), + EntityProjection.nonProjecting(entityClass), collectionName); + + QueryResultConverterCallback callback = new QueryResultConverterCallback<>(resultConverter, readCallback) { + + @Override + public Mono doWith(Document object) { + ids.add(object.get("_id")); + return super.doWith(object); + } + }; + + Flux flux = doFind(collectionName, ReactiveCollectionPreparerDelegate.of(query), query.getQueryObject(), + query.getFieldsObject(), entityClass, + new QueryFindPublisherPreparer(query, query.getSortObject(), query.getLimit(), query.getSkip(), entityClass), + callback); + + return Flux.from(flux).collectList().filter(it -> !it.isEmpty()).flatMapMany(list -> { + + Criteria[] criterias = ids.stream() // + .map(it -> Criteria.where("_id").is(it)) // + .toArray(Criteria[]::new); + + Query removeQuery = new Query(criterias.length == 1 ? criterias[0] : new Criteria().orOperator(criterias)); + if (query.hasReadPreference()) { + removeQuery.withReadPreference(query.getReadPreference()); + } + + return Flux.from(remove(removeQuery, entityClass, collectionName)) + .flatMapSequential(deleteResult -> Flux.fromIterable(list)); + }); + } + /** * Create the specified collection using the provided options * @@ -2487,9 +2537,10 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati new ReadDocumentCallback<>(this.mongoConverter, entityClass, collectionName), collectionName); } - protected Mono doFindAndModify(String collectionName, + Mono doFindAndModify(String collectionName, CollectionPreparer> collectionPreparer, Document query, Document fields, - @Nullable Document sort, Class entityClass, UpdateDefinition update, FindAndModifyOptions options) { + @Nullable Document sort, Class entityClass, UpdateDefinition update, FindAndModifyOptions options, + QueryResultConverter resultConverter) { MongoPersistentEntity entity = mappingContext.getPersistentEntity(entityClass); UpdateContext updateContext = queryOperations.updateSingleContext(update, query, false); @@ -2508,10 +2559,13 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati serializeToJsonSafely(mappedUpdate), collectionName)); } + EntityProjection projection = EntityProjection.nonProjecting(entityClass); + DocumentCallback callback = getResultReader(projection, collectionName, resultConverter); + return executeFindOneInternal( new FindAndModifyCallback(collectionPreparer, mappedQuery, fields, sort, mappedUpdate, update.getArrayFilters().stream().map(ArrayFilter::asDocument).collect(Collectors.toList()), options), - new ReadDocumentCallback<>(this.mongoConverter, entityClass, collectionName), collectionName); + callback, collectionName); }); } @@ -2540,7 +2594,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati EntityProjection projection = operations.introspectProjection(resultType, entityType); return doFindAndReplace(collectionName, collectionPreparer, mappedQuery, mappedFields, mappedSort, collation, - entityType, replacement, options, projection); + entityType, replacement, options, projection, QueryResultConverter.entity()); } /** @@ -2560,10 +2614,11 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati * {@literal false} and {@link FindAndReplaceOptions#isUpsert() upsert} is {@literal false}. * @since 3.4 */ - private Mono doFindAndReplace(String collectionName, + private Mono doFindAndReplace(String collectionName, CollectionPreparer> collectionPreparer, Document mappedQuery, Document mappedFields, Document mappedSort, com.mongodb.client.model.Collation collation, Class entityType, Document replacement, - FindAndReplaceOptions options, EntityProjection projection) { + FindAndReplaceOptions options, EntityProjection projection, + QueryResultConverter resultConverter) { return Mono.defer(() -> { @@ -2575,9 +2630,10 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati serializeToJsonSafely(replacement), collectionName)); } + DocumentCallback resultReader = getResultReader(projection, collectionName, resultConverter); + return executeFindOneInternal(new FindAndReplaceCallback(collectionPreparer, mappedQuery, mappedFields, - mappedSort, replacement, collation, options), - new ProjectingReadCallback<>(this.mongoConverter, projection, collectionName), collectionName); + mappedSort, replacement, collation, options), resultReader, collectionName); }); } @@ -3124,7 +3180,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati FindPublisher doInCollection(MongoCollection collection) throws MongoException, DataAccessException; } - static final class QueryResultConverterCallback implements DocumentCallback { + static class QueryResultConverterCallback implements DocumentCallback { private final QueryResultConverter converter; private final DocumentCallback delegate; diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveRemoveOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveRemoveOperation.java index 378f13d91..dd515cb37 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveRemoveOperation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveRemoveOperation.java @@ -20,6 +20,7 @@ import reactor.core.publisher.Mono; import org.springframework.data.mongodb.core.query.CriteriaDefinition; import org.springframework.data.mongodb.core.query.Query; +import org.springframework.lang.Contract; import com.mongodb.client.result.DeleteResult; @@ -56,16 +57,22 @@ public interface ReactiveRemoveOperation { ReactiveRemove remove(Class domainType); /** - * Compose remove execution by calling one of the terminating methods. + * @author Christoph Strobl + * @since 5.0 */ - interface TerminatingRemove { + interface TerminatingResults { /** - * Remove all documents matching. + * Map the query result to a different type using {@link QueryResultConverter}. * - * @return {@link Mono} emitting the {@link DeleteResult}. Never {@literal null}. + * @param {@link Class type} of the result. + * @param converter the converter, must not be {@literal null}. + * @return new instance of {@link ExecutableFindOperation.TerminatingResults}. + * @throws IllegalArgumentException if {@link QueryResultConverter converter} is {@literal null}. + * @since 5.0 */ - Mono all(); + @Contract("_ -> new") + TerminatingResults map(QueryResultConverter converter); /** * Remove and return all matching documents.
@@ -78,6 +85,20 @@ public interface ReactiveRemoveOperation { Flux findAndRemove(); } + /** + * Compose remove execution by calling one of the terminating methods. + */ + interface TerminatingRemove extends TerminatingResults { + + /** + * Remove all documents matching. + * + * @return {@link Mono} emitting the {@link DeleteResult}. Never {@literal null}. + */ + Mono all(); + + } + /** * Collection override (optional). */ diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveRemoveOperationSupport.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveRemoveOperationSupport.java index 5c935ec62..f77b5296d 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveRemoveOperationSupport.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveRemoveOperationSupport.java @@ -15,10 +15,10 @@ */ package org.springframework.data.mongodb.core; -import org.jspecify.annotations.Nullable; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import org.jspecify.annotations.Nullable; import org.springframework.data.mongodb.core.query.Query; import org.springframework.util.Assert; import org.springframework.util.StringUtils; @@ -47,22 +47,25 @@ class ReactiveRemoveOperationSupport implements ReactiveRemoveOperation { Assert.notNull(domainType, "DomainType must not be null"); - return new ReactiveRemoveSupport<>(template, domainType, ALL_QUERY, null); + return new ReactiveRemoveSupport<>(template, domainType, ALL_QUERY, null, QueryResultConverter.entity()); } - static class ReactiveRemoveSupport implements ReactiveRemove, RemoveWithCollection { + static class ReactiveRemoveSupport implements ReactiveRemove, RemoveWithCollection { private final ReactiveMongoTemplate template; - private final Class domainType; + private final Class domainType; private final Query query; private final @Nullable String collection; + private final QueryResultConverter resultConverter; - ReactiveRemoveSupport(ReactiveMongoTemplate template, Class domainType, Query query, @Nullable String collection) { + ReactiveRemoveSupport(ReactiveMongoTemplate template, Class domainType, Query query, @Nullable String collection, + QueryResultConverter resultConverter) { this.template = template; this.domainType = domainType; this.query = query; this.collection = collection; + this.resultConverter = resultConverter; } @Override @@ -70,7 +73,7 @@ class ReactiveRemoveOperationSupport implements ReactiveRemoveOperation { Assert.hasText(collection, "Collection must not be null nor empty"); - return new ReactiveRemoveSupport<>(template, domainType, query, collection); + return new ReactiveRemoveSupport<>(template, domainType, query, collection, resultConverter); } @Override @@ -78,7 +81,7 @@ class ReactiveRemoveOperationSupport implements ReactiveRemoveOperation { Assert.notNull(query, "Query must not be null"); - return new ReactiveRemoveSupport<>(template, domainType, query, collection); + return new ReactiveRemoveSupport<>(template, domainType, query, collection, resultConverter); } @Override @@ -94,7 +97,13 @@ class ReactiveRemoveOperationSupport implements ReactiveRemoveOperation { String collectionName = getCollectionName(); - return template.doFindAndDelete(collectionName, query, domainType); + return template.doFindAndDelete(collectionName, query, domainType, resultConverter); + } + + @Override + @SuppressWarnings({ "unchecked", "rawtypes" }) + public TerminatingResults map(QueryResultConverter converter) { + return new ReactiveRemoveSupport<>(template, (Class) domainType, query, collection, converter); } private String getCollectionName() { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveUpdateOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveUpdateOperation.java index 51f75f326..c9f92029c 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveUpdateOperation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveUpdateOperation.java @@ -15,6 +15,7 @@ */ package org.springframework.data.mongodb.core; +import org.jetbrains.annotations.Contract; import reactor.core.publisher.Mono; import org.springframework.data.mongodb.core.aggregation.AggregationUpdate; @@ -64,6 +65,18 @@ public interface ReactiveUpdateOperation { */ interface TerminatingFindAndModify { + /** + * Map the query result to a different type using {@link QueryResultConverter}. + * + * @param {@link Class type} of the result. + * @param converter the converter, must not be {@literal null}. + * @return new instance of {@link TerminatingFindAndModify}. + * @throws IllegalArgumentException if {@link QueryResultConverter converter} is {@literal null}. + * @since 5.0 + */ + @Contract("_ -> new") + TerminatingFindAndModify map(QueryResultConverter converter); + /** * Find, modify and return the first matching document. * @@ -97,6 +110,18 @@ public interface ReactiveUpdateOperation { */ interface TerminatingFindAndReplace extends TerminatingReplace { + /** + * Map the query result to a different type using {@link QueryResultConverter}. + * + * @param {@link Class type} of the result. + * @param converter the converter, must not be {@literal null}. + * @return new instance of {@link TerminatingFindAndModify}. + * @throws IllegalArgumentException if {@link QueryResultConverter converter} is {@literal null}. + * @since 5.0 + */ + @Contract("_ -> new") + TerminatingFindAndReplace map(QueryResultConverter converter); + /** * Find, replace and return the first matching document. * diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveUpdateOperationSupport.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveUpdateOperationSupport.java index 75bfeef31..876a7a5aa 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveUpdateOperationSupport.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveUpdateOperationSupport.java @@ -47,10 +47,10 @@ class ReactiveUpdateOperationSupport implements ReactiveUpdateOperation { Assert.notNull(domainType, "DomainType must not be null"); - return new ReactiveUpdateSupport<>(template, domainType, ALL_QUERY, null, null, null, null, null, domainType); + return new ReactiveUpdateSupport<>(template, domainType, ALL_QUERY, null, null, null, null, null, domainType, QueryResultConverter.entity()); } - static class ReactiveUpdateSupport + static class ReactiveUpdateSupport implements ReactiveUpdate, UpdateWithCollection, UpdateWithQuery, TerminatingUpdate, FindAndReplaceWithOptions, FindAndReplaceWithProjection, TerminatingFindAndReplace { @@ -62,11 +62,12 @@ class ReactiveUpdateOperationSupport implements ReactiveUpdateOperation { private final @Nullable FindAndModifyOptions findAndModifyOptions; private final @Nullable FindAndReplaceOptions findAndReplaceOptions; private final @Nullable Object replacement; - private final Class targetType; + private final Class targetType; + private final QueryResultConverter resultConverter; ReactiveUpdateSupport(ReactiveMongoTemplate template, Class domainType, Query query, @Nullable UpdateDefinition update, @Nullable String collection, @Nullable FindAndModifyOptions findAndModifyOptions, @Nullable FindAndReplaceOptions findAndReplaceOptions, - @Nullable Object replacement, Class targetType) { + @Nullable Object replacement, Class targetType, QueryResultConverter resultConverter) { this.template = template; this.domainType = domainType; @@ -77,6 +78,7 @@ class ReactiveUpdateOperationSupport implements ReactiveUpdateOperation { this.findAndReplaceOptions = findAndReplaceOptions; this.replacement = replacement; this.targetType = targetType; + this.resultConverter = resultConverter; } @Override @@ -85,7 +87,7 @@ class ReactiveUpdateOperationSupport implements ReactiveUpdateOperation { Assert.notNull(update, "Update must not be null"); return new ReactiveUpdateSupport<>(template, domainType, query, update, collection, findAndModifyOptions, - findAndReplaceOptions, replacement, targetType); + findAndReplaceOptions, replacement, targetType, resultConverter); } @Override @@ -94,7 +96,7 @@ class ReactiveUpdateOperationSupport implements ReactiveUpdateOperation { Assert.hasText(collection, "Collection must not be null nor empty"); return new ReactiveUpdateSupport<>(template, domainType, query, update, collection, findAndModifyOptions, - findAndReplaceOptions, replacement, targetType); + findAndReplaceOptions, replacement, targetType, resultConverter); } @Override @@ -108,14 +110,14 @@ class ReactiveUpdateOperationSupport implements ReactiveUpdateOperation { } @Override - @SuppressWarnings("NullAway") + @SuppressWarnings({"unchecked", "rawtypes", "NullAway"}) public Mono findAndModify() { String collectionName = getCollectionName(); return template.findAndModify(query, update, - findAndModifyOptions != null ? findAndModifyOptions : FindAndModifyOptions.none(), targetType, - collectionName); + findAndModifyOptions != null ? findAndModifyOptions : FindAndModifyOptions.none(), (Class) targetType, + collectionName, resultConverter); } @Override @@ -126,7 +128,7 @@ class ReactiveUpdateOperationSupport implements ReactiveUpdateOperation { return template.findAndReplace(query, replacement, findAndReplaceOptions != null ? findAndReplaceOptions : FindAndReplaceOptions.none(), (Class) domainType, - getCollectionName(), targetType); + getCollectionName(), targetType, resultConverter); } @Override @@ -135,7 +137,7 @@ class ReactiveUpdateOperationSupport implements ReactiveUpdateOperation { Assert.notNull(query, "Query must not be null"); return new ReactiveUpdateSupport<>(template, domainType, query, update, collection, findAndModifyOptions, - findAndReplaceOptions, replacement, targetType); + findAndReplaceOptions, replacement, targetType, resultConverter); } @Override @@ -149,7 +151,7 @@ class ReactiveUpdateOperationSupport implements ReactiveUpdateOperation { Assert.notNull(options, "Options must not be null"); return new ReactiveUpdateSupport<>(template, domainType, query, update, collection, options, - findAndReplaceOptions, replacement, targetType); + findAndReplaceOptions, replacement, targetType, resultConverter); } @Override @@ -158,7 +160,7 @@ class ReactiveUpdateOperationSupport implements ReactiveUpdateOperation { Assert.notNull(replacement, "Replacement must not be null"); return new ReactiveUpdateSupport<>(template, domainType, query, update, collection, findAndModifyOptions, - findAndReplaceOptions, replacement, targetType); + findAndReplaceOptions, replacement, targetType, resultConverter); } @Override @@ -167,7 +169,7 @@ class ReactiveUpdateOperationSupport implements ReactiveUpdateOperation { Assert.notNull(options, "Options must not be null"); return new ReactiveUpdateSupport<>(template, domainType, query, update, collection, findAndModifyOptions, options, - replacement, targetType); + replacement, targetType, resultConverter); } @Override @@ -178,7 +180,7 @@ class ReactiveUpdateOperationSupport implements ReactiveUpdateOperation { target.upsert(); } return new ReactiveUpdateSupport<>(template, domainType, query, update, collection, findAndModifyOptions, - target, replacement, targetType); + target, replacement, targetType, resultConverter); } @Override @@ -187,7 +189,13 @@ class ReactiveUpdateOperationSupport implements ReactiveUpdateOperation { Assert.notNull(resultType, "ResultType must not be null"); return new ReactiveUpdateSupport<>(template, domainType, query, update, collection, findAndModifyOptions, - findAndReplaceOptions, replacement, resultType); + findAndReplaceOptions, replacement, resultType, QueryResultConverter.entity()); + } + + @Override + public ReactiveUpdateSupport map(QueryResultConverter converter) { + return new ReactiveUpdateSupport<>(template, domainType, query, update, collection, findAndModifyOptions, + findAndReplaceOptions, replacement, targetType, this.resultConverter.andThen(converter)); } @Override diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveRemoveOperationSupportTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveRemoveOperationSupportTests.java index 565986970..cfdc5fe1a 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveRemoveOperationSupportTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveRemoveOperationSupportTests.java @@ -15,13 +15,14 @@ */ package org.springframework.data.mongodb.core; -import static org.assertj.core.api.Assertions.*; -import static org.springframework.data.mongodb.core.query.Criteria.*; -import static org.springframework.data.mongodb.core.query.Query.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.data.mongodb.core.query.Criteria.where; +import static org.springframework.data.mongodb.core.query.Query.query; import reactor.test.StepVerifier; import java.util.Objects; +import java.util.Optional; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -108,6 +109,15 @@ class ReactiveRemoveOperationSupportTests { .expectNext(han).verifyComplete(); } + @Test // GH-4949 + void removeConvertAndReturnAllMatching() { + + template.remove(Person.class).matching(query(where("firstname").is("han"))).map((raw, it) -> Optional.of(it.get())) + .findAndRemove().as(StepVerifier::create).expectNext(Optional.of(han)).verifyComplete(); + + template.findById(han.id, Person.class).as(StepVerifier::create).verifyComplete(); + } + @org.springframework.data.mongodb.core.mapping.Document(collection = STAR_WARS) static class Person { diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveUpdateOperationSupportTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveUpdateOperationSupportTests.java index 3ac99c2b6..bef67501b 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveUpdateOperationSupportTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveUpdateOperationSupportTests.java @@ -15,13 +15,15 @@ */ package org.springframework.data.mongodb.core; -import static org.assertj.core.api.Assertions.*; -import static org.springframework.data.mongodb.core.query.Criteria.*; -import static org.springframework.data.mongodb.core.query.Query.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.springframework.data.mongodb.core.query.Criteria.where; +import static org.springframework.data.mongodb.core.query.Query.query; import reactor.test.StepVerifier; import java.util.Objects; +import java.util.Optional; import org.bson.BsonString; import org.junit.jupiter.api.BeforeEach; @@ -175,6 +177,18 @@ class ReactiveUpdateOperationSupportTests { "Han"); } + @Test // GH-4949 + void findAndModifyWithWithResultConversion() { + + template.update(Jedi.class).inCollection(STAR_WARS).matching(query(where("_id").is(han.getId()))) + .apply(new Update().set("name", "Han")).map((raw, it) -> Optional.of(it.get())).findAndModify() + .as(StepVerifier::create).consumeNextWith(actual -> assertThat(actual.get().getName()).isEqualTo("han")) + .verifyComplete(); + + assertThat(blocking.findOne(queryHan(), Person.class)).isNotEqualTo(han).hasFieldOrPropertyWithValue("firstname", + "Han"); + } + @Test // DATAMONGO-1719 void findAndModifyWithOptions() { @@ -225,6 +239,18 @@ class ReactiveUpdateOperationSupportTests { }).verifyComplete(); } + @Test // GH-4949 + void findAndReplaceWithResultConversion() { + + Person luke = new Person(); + luke.firstname = "Luke"; + + template.update(Person.class).matching(queryHan()).replaceWith(luke).map((raw, it) -> Optional.of(it.get())).findAndReplace() // + .as(StepVerifier::create).consumeNextWith(it -> { + assertThat(it.get().getFirstname()).isEqualTo(han.firstname); + }).verifyComplete(); + } + @Test // DATAMONGO-1827 void findAndReplaceWithCollection() { diff --git a/src/main/antora/modules/ROOT/pages/mongodb/template-api.adoc b/src/main/antora/modules/ROOT/pages/mongodb/template-api.adoc index f2a7a19bd..ece61559e 100644 --- a/src/main/antora/modules/ROOT/pages/mongodb/template-api.adoc +++ b/src/main/antora/modules/ROOT/pages/mongodb/template-api.adoc @@ -116,6 +116,19 @@ WARNING: Projections must not be applied to xref:mongodb/mapping/document-refere You can switch between retrieving a single entity and retrieving multiple entities as a `List` or a `Stream` through the terminating methods: `first()`, `one()`, `all()`, or `stream()`. +Results can be contextually post-processed by using a `QueryResultConverter` that has access to both the raw result `Document` and the already mapped object by calling `map(...)` as outlined below. + +[source,java] +==== +---- +List> result = template.query(Person.class) + .as(Jedi.class) + .matching(query(where("firstname").is("luke"))) + .map((document, reader) -> Optional.of(reader.get())) + .all(); +---- +==== + When writing a geo-spatial query with `near(NearQuery)`, the number of terminating methods is altered to include only the methods that are valid for running a `geoNear` command in MongoDB (fetching entities as a `GeoResult` within `GeoResults`), as the following example shows: [tabs]