Browse Source

Support QueryResultConverter for reactive delete, replace and update operations.

Original Pull Request: #4949
pull/4976/head
Christoph Strobl 7 months ago committed by Mark Paluch
parent
commit
50aca14d1f
No known key found for this signature in database
GPG Key ID: 55BC6374BAA9D973
  1. 4
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableRemoveOperation.java
  2. 1
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableRemoveOperationSupport.java
  3. 90
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java
  4. 31
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveRemoveOperation.java
  5. 25
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveRemoveOperationSupport.java
  6. 25
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveUpdateOperation.java
  7. 40
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveUpdateOperationSupport.java
  8. 16
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveRemoveOperationSupportTests.java
  9. 32
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveUpdateOperationSupportTests.java
  10. 13
      src/main/antora/modules/ROOT/pages/mongodb/template-api.adoc

4
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableRemoveOperation.java

@ -55,6 +55,10 @@ public interface ExecutableRemoveOperation { @@ -55,6 +55,10 @@ public interface ExecutableRemoveOperation {
*/
<T> ExecutableRemove<T> remove(Class<T> domainType);
/**
* @author Christoph Strobl
* @since 5.0
*/
interface TerminatingResults<T> {
/**

1
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableRemoveOperationSupport.java

@ -109,6 +109,7 @@ class ExecutableRemoveOperationSupport implements ExecutableRemoveOperation { @@ -109,6 +109,7 @@ class ExecutableRemoveOperationSupport implements ExecutableRemoveOperation {
}
@Override
@SuppressWarnings({"unchecked", "rawtypes"})
public <R> TerminatingResults<R> map(QueryResultConverter<? super T, ? extends R> converter) {
return new ExecutableRemoveSupport<>(template, (Class) domainType, query, collection, converter);
}

90
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java

@ -47,7 +47,6 @@ import org.bson.types.ObjectId; @@ -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.*; @@ -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 @@ -1137,9 +1137,8 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
return findAndModify(query, update, options, entityClass, getCollectionName(entityClass));
}
@Override
public <T> Mono<T> findAndModify(Query query, UpdateDefinition update, FindAndModifyOptions options,
Class<T> entityClass, String collectionName) {
public <S, T> Mono<T> findAndModify(Query query, UpdateDefinition update, FindAndModifyOptions options,
Class<S> entityClass, String collectionName, QueryResultConverter<? super S, ? extends T> 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 @@ -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 <T> Mono<T> findAndModify(Query query, UpdateDefinition update, FindAndModifyOptions options,
Class<T> entityClass, String collectionName) {
return findAndModify(query, update, options, entityClass, collectionName, QueryResultConverter.entity());
}
@Override
@SuppressWarnings("NullAway")
public <S, T> Mono<T> findAndReplace(Query query, S replacement, FindAndReplaceOptions options, Class<S> entityType,
String collectionName, Class<T> resultType) {
return findAndReplace(query, replacement, options, entityType, collectionName, resultType,
QueryResultConverter.entity());
}
@SuppressWarnings("NullAway")
public <S, T, R> Mono<R> findAndReplace(Query query, S replacement, FindAndReplaceOptions options,
Class<S> entityType, String collectionName, Class<T> resultType,
QueryResultConverter<? super T, ? extends R> 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 @@ -1199,9 +1212,9 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
mapped.getCollection()));
}).flatMap(it -> {
Mono<T> afterFindAndReplace = doFindAndReplace(it.getCollection(), collectionPreparer, mappedQuery,
Mono<R> 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 @@ -2280,6 +2293,43 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
.flatMapSequential(deleteResult -> Flux.fromIterable(list)));
}
<S, T> Flux<T> doFindAndDelete(String collectionName, Query query, Class<S> entityClass,
QueryResultConverter<? super S, ? extends T> resultConverter) {
List<Object> ids = new ArrayList<>();
ProjectingReadCallback readCallback = new ProjectingReadCallback(getConverter(),
EntityProjection.nonProjecting(entityClass), collectionName);
QueryResultConverterCallback<S, T> callback = new QueryResultConverterCallback<>(resultConverter, readCallback) {
@Override
public Mono<T> doWith(Document object) {
ids.add(object.get("_id"));
return super.doWith(object);
}
};
Flux<T> 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 @@ -2487,9 +2537,10 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
new ReadDocumentCallback<>(this.mongoConverter, entityClass, collectionName), collectionName);
}
protected <T> Mono<T> doFindAndModify(String collectionName,
<S, T> Mono<T> doFindAndModify(String collectionName,
CollectionPreparer<MongoCollection<Document>> collectionPreparer, Document query, Document fields,
@Nullable Document sort, Class<T> entityClass, UpdateDefinition update, FindAndModifyOptions options) {
@Nullable Document sort, Class<S> entityClass, UpdateDefinition update, FindAndModifyOptions options,
QueryResultConverter<? super S, ? extends T> resultConverter) {
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(entityClass);
UpdateContext updateContext = queryOperations.updateSingleContext(update, query, false);
@ -2508,10 +2559,13 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati @@ -2508,10 +2559,13 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
serializeToJsonSafely(mappedUpdate), collectionName));
}
EntityProjection<S, ?> projection = EntityProjection.nonProjecting(entityClass);
DocumentCallback<T> 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 @@ -2540,7 +2594,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
EntityProjection<T, ?> 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 @@ -2560,10 +2614,11 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
* {@literal false} and {@link FindAndReplaceOptions#isUpsert() upsert} is {@literal false}.
* @since 3.4
*/
private <T> Mono<T> doFindAndReplace(String collectionName,
private <S, T> Mono<T> doFindAndReplace(String collectionName,
CollectionPreparer<MongoCollection<Document>> collectionPreparer, Document mappedQuery, Document mappedFields,
Document mappedSort, com.mongodb.client.model.Collation collation, Class<?> entityType, Document replacement,
FindAndReplaceOptions options, EntityProjection<T, ?> projection) {
FindAndReplaceOptions options, EntityProjection<S, ?> projection,
QueryResultConverter<? super S, ? extends T> resultConverter) {
return Mono.defer(() -> {
@ -2575,9 +2630,10 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati @@ -2575,9 +2630,10 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
serializeToJsonSafely(replacement), collectionName));
}
DocumentCallback<T> 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 @@ -3124,7 +3180,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
FindPublisher<T> doInCollection(MongoCollection<Document> collection) throws MongoException, DataAccessException;
}
static final class QueryResultConverterCallback<T, R> implements DocumentCallback<R> {
static class QueryResultConverterCallback<T, R> implements DocumentCallback<R> {
private final QueryResultConverter<? super T, ? extends R> converter;
private final DocumentCallback<T> delegate;

31
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveRemoveOperation.java

@ -20,6 +20,7 @@ import reactor.core.publisher.Mono; @@ -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 { @@ -56,16 +57,22 @@ public interface ReactiveRemoveOperation {
<T> ReactiveRemove<T> remove(Class<T> domainType);
/**
* Compose remove execution by calling one of the terminating methods.
* @author Christoph Strobl
* @since 5.0
*/
interface TerminatingRemove<T> {
interface TerminatingResults<T> {
/**
* 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 <R> {@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<DeleteResult> all();
@Contract("_ -> new")
<R> TerminatingResults<R> map(QueryResultConverter<? super T, ? extends R> converter);
/**
* Remove and return all matching documents. <br/>
@ -78,6 +85,20 @@ public interface ReactiveRemoveOperation { @@ -78,6 +85,20 @@ public interface ReactiveRemoveOperation {
Flux<T> findAndRemove();
}
/**
* Compose remove execution by calling one of the terminating methods.
*/
interface TerminatingRemove<T> extends TerminatingResults<T> {
/**
* Remove all documents matching.
*
* @return {@link Mono} emitting the {@link DeleteResult}. Never {@literal null}.
*/
Mono<DeleteResult> all();
}
/**
* Collection override (optional).
*/

25
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveRemoveOperationSupport.java

@ -15,10 +15,10 @@ @@ -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 { @@ -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<T> implements ReactiveRemove<T>, RemoveWithCollection<T> {
static class ReactiveRemoveSupport<S, T> implements ReactiveRemove<T>, RemoveWithCollection<T> {
private final ReactiveMongoTemplate template;
private final Class<T> domainType;
private final Class<S> domainType;
private final Query query;
private final @Nullable String collection;
private final QueryResultConverter<? super S, ? extends T> resultConverter;
ReactiveRemoveSupport(ReactiveMongoTemplate template, Class<T> domainType, Query query, @Nullable String collection) {
ReactiveRemoveSupport(ReactiveMongoTemplate template, Class<S> domainType, Query query, @Nullable String collection,
QueryResultConverter<? super S, ? extends T> 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 { @@ -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 { @@ -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 { @@ -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 <R> TerminatingResults<R> map(QueryResultConverter<? super T, ? extends R> converter) {
return new ReactiveRemoveSupport<>(template, (Class) domainType, query, collection, converter);
}
private String getCollectionName() {

25
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveUpdateOperation.java

@ -15,6 +15,7 @@ @@ -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 { @@ -64,6 +65,18 @@ public interface ReactiveUpdateOperation {
*/
interface TerminatingFindAndModify<T> {
/**
* Map the query result to a different type using {@link QueryResultConverter}.
*
* @param <R> {@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")
<R> TerminatingFindAndModify<R> map(QueryResultConverter<? super T, ? extends R> converter);
/**
* Find, modify and return the first matching document.
*
@ -97,6 +110,18 @@ public interface ReactiveUpdateOperation { @@ -97,6 +110,18 @@ public interface ReactiveUpdateOperation {
*/
interface TerminatingFindAndReplace<T> extends TerminatingReplace {
/**
* Map the query result to a different type using {@link QueryResultConverter}.
*
* @param <R> {@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")
<R> TerminatingFindAndReplace<R> map(QueryResultConverter<? super T, ? extends R> converter);
/**
* Find, replace and return the first matching document.
*

40
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveUpdateOperationSupport.java

@ -47,10 +47,10 @@ class ReactiveUpdateOperationSupport implements ReactiveUpdateOperation { @@ -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<T>
static class ReactiveUpdateSupport<S, T>
implements ReactiveUpdate<T>, UpdateWithCollection<T>, UpdateWithQuery<T>, TerminatingUpdate<T>,
FindAndReplaceWithOptions<T>, FindAndReplaceWithProjection<T>, TerminatingFindAndReplace<T> {
@ -62,11 +62,12 @@ class ReactiveUpdateOperationSupport implements ReactiveUpdateOperation { @@ -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<T> targetType;
private final Class<S> targetType;
private final QueryResultConverter<? super S, ? extends T> resultConverter;
ReactiveUpdateSupport(ReactiveMongoTemplate template, Class<?> domainType, Query query, @Nullable UpdateDefinition update,
@Nullable String collection, @Nullable FindAndModifyOptions findAndModifyOptions, @Nullable FindAndReplaceOptions findAndReplaceOptions,
@Nullable Object replacement, Class<T> targetType) {
@Nullable Object replacement, Class<S> targetType, QueryResultConverter<? super S, ? extends T> resultConverter) {
this.template = template;
this.domainType = domainType;
@ -77,6 +78,7 @@ class ReactiveUpdateOperationSupport implements ReactiveUpdateOperation { @@ -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 { @@ -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 { @@ -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 { @@ -108,14 +110,14 @@ class ReactiveUpdateOperationSupport implements ReactiveUpdateOperation {
}
@Override
@SuppressWarnings("NullAway")
@SuppressWarnings({"unchecked", "rawtypes", "NullAway"})
public Mono<T> 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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 <R> ReactiveUpdateSupport<S, R> map(QueryResultConverter<? super T, ? extends R> converter) {
return new ReactiveUpdateSupport<>(template, domainType, query, update, collection, findAndModifyOptions,
findAndReplaceOptions, replacement, targetType, this.resultConverter.andThen(converter));
}
@Override

16
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveRemoveOperationSupportTests.java

@ -15,13 +15,14 @@ @@ -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 { @@ -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 {

32
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveUpdateOperationSupportTests.java

@ -15,13 +15,15 @@ @@ -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 { @@ -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 { @@ -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() {

13
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 @@ -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<Optional<Jedi>> 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]

Loading…
Cancel
Save