Browse Source

Support QueryResultConverter for delete, replace and update operations.

Original Pull Request: #4949
pull/4976/head
Christoph Strobl 8 months ago committed by Mark Paluch
parent
commit
ec49c14bb9
No known key found for this signature in database
GPG Key ID: 55BC6374BAA9D973
  1. 39
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableRemoveOperation.java
  2. 23
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableRemoveOperationSupport.java
  3. 27
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableUpdateOperation.java
  4. 50
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableUpdateOperationSupport.java
  5. 87
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java
  6. 9
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ExecutableRemoveOperationSupportTests.java
  7. 24
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ExecutableUpdateOperationSupportTests.java
  8. 4
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateUnitTests.java
  9. 89
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/QueryResultConverterUnitTests.java

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

@ -19,6 +19,7 @@ import java.util.List; @@ -19,6 +19,7 @@ import java.util.List;
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;
@ -54,11 +55,36 @@ public interface ExecutableRemoveOperation { @@ -54,11 +55,36 @@ public interface ExecutableRemoveOperation {
*/
<T> ExecutableRemove<T> remove(Class<T> domainType);
interface TerminatingResults<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 ExecutableFindOperation.TerminatingResults}.
* @throws IllegalArgumentException if {@link QueryResultConverter converter} is {@literal null}.
* @since x.y
*/
@Contract("_ -> new")
<R> TerminatingResults<R> map(QueryResultConverter<? super T, ? extends R> converter);
/**
* Remove and return all matching documents. <br/>
* <strong>NOTE:</strong> The entire list of documents will be fetched before sending the actual delete commands.
* Also, {@link org.springframework.context.ApplicationEvent}s will be published for each and every delete
* operation.
*
* @return empty {@link List} if no match found. Never {@literal null}.
*/
List<T> findAndRemove();
}
/**
* @author Christoph Strobl
* @since 2.0
*/
interface TerminatingRemove<T> {
interface TerminatingRemove<T> extends TerminatingResults<T> {
/**
* Remove all documents matching.
@ -73,16 +99,6 @@ public interface ExecutableRemoveOperation { @@ -73,16 +99,6 @@ public interface ExecutableRemoveOperation {
* @return the {@link DeleteResult}. Never {@literal null}.
*/
DeleteResult one();
/**
* Remove and return all matching documents. <br/>
* <strong>NOTE:</strong> The entire list of documents will be fetched before sending the actual delete commands.
* Also, {@link org.springframework.context.ApplicationEvent}s will be published for each and every delete
* operation.
*
* @return empty {@link List} if no match found. Never {@literal null}.
*/
List<T> findAndRemove();
}
/**
@ -105,7 +121,6 @@ public interface ExecutableRemoveOperation { @@ -105,7 +121,6 @@ public interface ExecutableRemoveOperation {
RemoveWithQuery<T> inCollection(String collection);
}
/**
* @author Christoph Strobl
* @since 2.0

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

@ -48,26 +48,28 @@ class ExecutableRemoveOperationSupport implements ExecutableRemoveOperation { @@ -48,26 +48,28 @@ class ExecutableRemoveOperationSupport implements ExecutableRemoveOperation {
Assert.notNull(domainType, "DomainType must not be null");
return new ExecutableRemoveSupport<>(tempate, domainType, ALL_QUERY, null);
return new ExecutableRemoveSupport<>(tempate, domainType, ALL_QUERY, null, QueryResultConverter.entity());
}
/**
* @author Christoph Strobl
* @since 2.0
*/
static class ExecutableRemoveSupport<T> implements ExecutableRemove<T>, RemoveWithCollection<T> {
static class ExecutableRemoveSupport<S, T> implements ExecutableRemove<T>, RemoveWithCollection<T> {
private final MongoTemplate template;
private final Class<T> domainType;
private final Class<S> domainType;
private final Query query;
@Nullable private final String collection;
private final QueryResultConverter<? super S, ? extends T> resultConverter;
public ExecutableRemoveSupport(MongoTemplate template, Class<T> domainType, Query query,
@Nullable String collection) {
public ExecutableRemoveSupport(MongoTemplate 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
@ -76,7 +78,7 @@ class ExecutableRemoveOperationSupport implements ExecutableRemoveOperation { @@ -76,7 +78,7 @@ class ExecutableRemoveOperationSupport implements ExecutableRemoveOperation {
Assert.hasText(collection, "Collection must not be null nor empty");
return new ExecutableRemoveSupport<>(template, domainType, query, collection);
return new ExecutableRemoveSupport<>(template, domainType, query, collection, resultConverter);
}
@Override
@ -85,7 +87,7 @@ class ExecutableRemoveOperationSupport implements ExecutableRemoveOperation { @@ -85,7 +87,7 @@ class ExecutableRemoveOperationSupport implements ExecutableRemoveOperation {
Assert.notNull(query, "Query must not be null");
return new ExecutableRemoveSupport<>(template, domainType, query, collection);
return new ExecutableRemoveSupport<>(template, domainType, query, collection, resultConverter);
}
@Override
@ -103,7 +105,12 @@ class ExecutableRemoveOperationSupport implements ExecutableRemoveOperation { @@ -103,7 +105,12 @@ class ExecutableRemoveOperationSupport implements ExecutableRemoveOperation {
String collectionName = getCollectionName();
return template.doFindAndDelete(collectionName, query, domainType);
return template.doFindAndDelete(collectionName, query, domainType, resultConverter);
}
@Override
public <R> TerminatingResults<R> map(QueryResultConverter<? super T, ? extends R> converter) {
return new ExecutableRemoveSupport<>(template, (Class) domainType, query, collection, converter);
}
private String getCollectionName() {

27
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableUpdateOperation.java

@ -18,6 +18,7 @@ package org.springframework.data.mongodb.core; @@ -18,6 +18,7 @@ package org.springframework.data.mongodb.core;
import java.util.Optional;
import org.jspecify.annotations.Nullable;
import org.springframework.data.mongodb.core.ExecutableFindOperation.TerminatingResults;
import org.springframework.data.mongodb.core.aggregation.AggregationUpdate;
import org.springframework.data.mongodb.core.query.CriteriaDefinition;
import org.springframework.data.mongodb.core.query.Query;
@ -25,6 +26,7 @@ import org.springframework.data.mongodb.core.query.Update; @@ -25,6 +26,7 @@ import org.springframework.data.mongodb.core.query.Update;
import org.springframework.data.mongodb.core.query.UpdateDefinition;
import com.mongodb.client.result.UpdateResult;
import org.springframework.lang.Contract;
/**
* {@link ExecutableUpdateOperation} allows creation and execution of MongoDB update / findAndModify / findAndReplace
@ -69,6 +71,19 @@ public interface ExecutableUpdateOperation { @@ -69,6 +71,19 @@ public interface ExecutableUpdateOperation {
*/
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 x.y
*/
@Contract("_ -> new")
<R> TerminatingFindAndModify<R> map(QueryResultConverter<? super T, ? extends R> converter);
/**
* Find, modify and return the first matching document.
*
@ -130,6 +145,18 @@ public interface ExecutableUpdateOperation { @@ -130,6 +145,18 @@ public interface ExecutableUpdateOperation {
*/
@Nullable
T findAndReplaceValue();
/**
* 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 x.y
*/
@Contract("_ -> new")
<R> TerminatingFindAndReplace<R> mapResult(QueryResultConverter<? super T, ? extends R> converter);
}
/**

50
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableUpdateOperationSupport.java

@ -47,7 +47,7 @@ class ExecutableUpdateOperationSupport implements ExecutableUpdateOperation { @@ -47,7 +47,7 @@ class ExecutableUpdateOperationSupport implements ExecutableUpdateOperation {
Assert.notNull(domainType, "DomainType must not be null");
return new ExecutableUpdateSupport<>(template, domainType, ALL_QUERY, null, null, null, null, null, domainType);
return new ExecutableUpdateSupport<>(template, domainType, ALL_QUERY, null, null, null, null, null, domainType, QueryResultConverter.entity());
}
/**
@ -55,23 +55,25 @@ class ExecutableUpdateOperationSupport implements ExecutableUpdateOperation { @@ -55,23 +55,25 @@ class ExecutableUpdateOperationSupport implements ExecutableUpdateOperation {
* @since 2.0
*/
@SuppressWarnings("rawtypes")
static class ExecutableUpdateSupport<T>
static class ExecutableUpdateSupport<S, T>
implements ExecutableUpdate<T>, UpdateWithCollection<T>, UpdateWithQuery<T>, TerminatingUpdate<T>,
FindAndReplaceWithOptions<T>, TerminatingFindAndReplace<T>, FindAndReplaceWithProjection<T> {
private final MongoTemplate template;
private final Class domainType;
private final Class<?> domainType;
private final Query query;
@Nullable private final UpdateDefinition update;
@Nullable private final String collection;
@Nullable private final FindAndModifyOptions findAndModifyOptions;
@Nullable private final FindAndReplaceOptions findAndReplaceOptions;
@Nullable private final Object replacement;
private final Class<T> targetType;
private final QueryResultConverter<? super S, ? extends T> resultConverter;
private final Class<S> targetType;
ExecutableUpdateSupport(MongoTemplate template, Class domainType, Query query, @Nullable UpdateDefinition update,
ExecutableUpdateSupport(MongoTemplate template, Class<?> domainType, Query query, @Nullable UpdateDefinition update,
@Nullable String collection, @Nullable FindAndModifyOptions findAndModifyOptions,
@Nullable FindAndReplaceOptions findAndReplaceOptions, @Nullable Object replacement, Class<T> targetType) {
@Nullable FindAndReplaceOptions findAndReplaceOptions, @Nullable Object replacement, Class<S> targetType,
QueryResultConverter<? super S, ? extends T> resultConverter) {
this.template = template;
this.domainType = domainType;
@ -82,6 +84,7 @@ class ExecutableUpdateOperationSupport implements ExecutableUpdateOperation { @@ -82,6 +84,7 @@ class ExecutableUpdateOperationSupport implements ExecutableUpdateOperation {
this.findAndReplaceOptions = findAndReplaceOptions;
this.replacement = replacement;
this.targetType = targetType;
this.resultConverter = resultConverter;
}
@Override
@ -91,7 +94,7 @@ class ExecutableUpdateOperationSupport implements ExecutableUpdateOperation { @@ -91,7 +94,7 @@ class ExecutableUpdateOperationSupport implements ExecutableUpdateOperation {
Assert.notNull(update, "Update must not be null");
return new ExecutableUpdateSupport<>(template, domainType, query, update, collection, findAndModifyOptions,
findAndReplaceOptions, replacement, targetType);
findAndReplaceOptions, replacement, targetType, resultConverter);
}
@Override
@ -101,7 +104,7 @@ class ExecutableUpdateOperationSupport implements ExecutableUpdateOperation { @@ -101,7 +104,7 @@ class ExecutableUpdateOperationSupport implements ExecutableUpdateOperation {
Assert.hasText(collection, "Collection must not be null nor empty");
return new ExecutableUpdateSupport<>(template, domainType, query, update, collection, findAndModifyOptions,
findAndReplaceOptions, replacement, targetType);
findAndReplaceOptions, replacement, targetType, resultConverter);
}
@Override
@ -111,7 +114,7 @@ class ExecutableUpdateOperationSupport implements ExecutableUpdateOperation { @@ -111,7 +114,7 @@ class ExecutableUpdateOperationSupport implements ExecutableUpdateOperation {
Assert.notNull(options, "Options must not be null");
return new ExecutableUpdateSupport<>(template, domainType, query, update, collection, options,
findAndReplaceOptions, replacement, targetType);
findAndReplaceOptions, replacement, targetType, resultConverter);
}
@Override
@ -121,7 +124,7 @@ class ExecutableUpdateOperationSupport implements ExecutableUpdateOperation { @@ -121,7 +124,7 @@ class ExecutableUpdateOperationSupport implements ExecutableUpdateOperation {
Assert.notNull(replacement, "Replacement must not be null");
return new ExecutableUpdateSupport<>(template, domainType, query, update, collection, findAndModifyOptions,
findAndReplaceOptions, replacement, targetType);
findAndReplaceOptions, replacement, targetType, resultConverter);
}
@Override
@ -131,7 +134,7 @@ class ExecutableUpdateOperationSupport implements ExecutableUpdateOperation { @@ -131,7 +134,7 @@ class ExecutableUpdateOperationSupport implements ExecutableUpdateOperation {
Assert.notNull(options, "Options must not be null");
return new ExecutableUpdateSupport<>(template, domainType, query, update, collection, findAndModifyOptions,
options, replacement, targetType);
options, replacement, targetType, resultConverter);
}
@Override
@ -143,7 +146,7 @@ class ExecutableUpdateOperationSupport implements ExecutableUpdateOperation { @@ -143,7 +146,7 @@ class ExecutableUpdateOperationSupport implements ExecutableUpdateOperation {
target.upsert();
}
return new ExecutableUpdateSupport<>(template, domainType, query, update, collection, findAndModifyOptions,
target, replacement, targetType);
target, replacement, targetType, resultConverter);
}
@Override
@ -153,7 +156,7 @@ class ExecutableUpdateOperationSupport implements ExecutableUpdateOperation { @@ -153,7 +156,7 @@ class ExecutableUpdateOperationSupport implements ExecutableUpdateOperation {
Assert.notNull(query, "Query must not be null");
return new ExecutableUpdateSupport<>(template, domainType, query, update, collection, findAndModifyOptions,
findAndReplaceOptions, replacement, targetType);
findAndReplaceOptions, replacement, targetType, resultConverter);
}
@Override
@ -163,7 +166,7 @@ class ExecutableUpdateOperationSupport implements ExecutableUpdateOperation { @@ -163,7 +166,7 @@ class ExecutableUpdateOperationSupport implements ExecutableUpdateOperation {
Assert.notNull(resultType, "ResultType must not be null");
return new ExecutableUpdateSupport<>(template, domainType, query, update, collection, findAndModifyOptions,
findAndReplaceOptions, replacement, resultType);
findAndReplaceOptions, replacement, resultType, QueryResultConverter.entity());
}
@Override
@ -181,13 +184,26 @@ class ExecutableUpdateOperationSupport implements ExecutableUpdateOperation { @@ -181,13 +184,26 @@ class ExecutableUpdateOperationSupport implements ExecutableUpdateOperation {
return doUpdate(true, true);
}
@Override
public <R> TerminatingFindAndModify<R> map(QueryResultConverter<? super T, ? extends R> converter) {
return new ExecutableUpdateSupport<>(template, domainType, query, update, collection, findAndModifyOptions,
findAndReplaceOptions, replacement, targetType, this.resultConverter.andThen(converter));
}
@Override
public <R> TerminatingFindAndReplace<R> mapResult(QueryResultConverter<? super T, ? extends R> converter) {
return new ExecutableUpdateSupport<>(template, domainType, query, update, collection, findAndModifyOptions,
findAndReplaceOptions, replacement, targetType, this.resultConverter.andThen(converter));
}
@Override
@SuppressWarnings("NullAway")
public @Nullable T findAndModifyValue() {
return template.findAndModify(query, update,
findAndModifyOptions != null ? findAndModifyOptions : new FindAndModifyOptions(), targetType,
getCollectionName());
getCollectionName(), resultConverter);
}
@Override
@ -195,8 +211,8 @@ class ExecutableUpdateOperationSupport implements ExecutableUpdateOperation { @@ -195,8 +211,8 @@ class ExecutableUpdateOperationSupport implements ExecutableUpdateOperation {
public @Nullable T findAndReplaceValue() {
return (T) template.findAndReplace(query, replacement,
findAndReplaceOptions != null ? findAndReplaceOptions : FindAndReplaceOptions.empty(), domainType,
getCollectionName(), targetType);
findAndReplaceOptions != null ? findAndReplaceOptions : FindAndReplaceOptions.empty(), (Class) domainType,
getCollectionName(), targetType, (QueryResultConverter) resultConverter);
}
@Override

87
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java

@ -122,6 +122,7 @@ import org.springframework.data.mongodb.core.mapreduce.MapReduceOptions; @@ -122,6 +122,7 @@ import org.springframework.data.mongodb.core.mapreduce.MapReduceOptions;
import org.springframework.data.mongodb.core.mapreduce.MapReduceResults;
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;
@ -1144,6 +1145,12 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, @@ -1144,6 +1145,12 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
@Override
public <T> T findAndModify(Query query, UpdateDefinition update, FindAndModifyOptions options,
Class<T> entityClass, String collectionName) {
return findAndModify(query, update, options, entityClass, collectionName, QueryResultConverter.entity());
}
<S, T> T findAndModify(Query query, UpdateDefinition update, FindAndModifyOptions options,
Class<S> entityClass, String collectionName, QueryResultConverter<? super S, ? extends T> resultConverter) {
Assert.notNull(query, "Query must not be null");
Assert.notNull(update, "Update must not be null");
@ -1163,12 +1170,17 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, @@ -1163,12 +1170,17 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
}
return doFindAndModify(createDelegate(query), collectionName, query.getQueryObject(), query.getFieldsObject(),
getMappedSortObject(query, entityClass), entityClass, update, optionsToUse);
getMappedSortObject(query, entityClass), entityClass, update, optionsToUse, resultConverter);
}
@Override
public <S, T> @Nullable 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());
}
public <S, T, R> @Nullable 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");
@ -1195,8 +1207,9 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, @@ -1195,8 +1207,9 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
maybeEmitEvent(new BeforeSaveEvent<>(replacement, mappedReplacement, collectionName));
maybeCallBeforeSave(replacement, mappedReplacement, collectionName);
T saved = doFindAndReplace(collectionPreparer, collectionName, mappedQuery, mappedFields, mappedSort,
queryContext.getCollation(entityType).orElse(null), entityType, mappedReplacement, options, projection);
R saved = doFindAndReplace(collectionPreparer, collectionName, mappedQuery, mappedFields, mappedSort,
queryContext.getCollation(entityType).orElse(null), entityType, mappedReplacement, options, projection, resultConverter);
if (saved != null) {
maybeEmitEvent(new AfterSaveEvent<>(saved, mappedReplacement, collectionName));
@ -2187,17 +2200,48 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, @@ -2187,17 +2200,48 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
*/
@SuppressWarnings("NullAway")
protected <T> List<T> doFindAndDelete(String collectionName, Query query, Class<T> entityClass) {
return doFindAndDelete(collectionName, query, entityClass, QueryResultConverter.entity());
}
protected <S,T> List<T> doFindAndDelete(String collectionName, Query query, Class<S> entityClass, QueryResultConverter<? super S, ? extends T> resultConverter) {
List<Object> ids = new ArrayList<>();
// QueryResultConverter<S,T> tmpConverter = new QueryResultConverter<S, S>() {
// @Override
// public S mapDocument(Document document, ConversionResultSupplier<S> reader) {
// ids.add(document.get("_id"));
// return reader.get();
// }
// }.andThen(resultConverter);
List<T> result = find(query, entityClass, collectionName);
// DocumentCallback<T> callback = getResultReader(EntityProjection.nonProjecting(entityClass), collectionName, tmpConverter);
QueryResultConverterCallback callback = new QueryResultConverterCallback(resultConverter, new ProjectingReadCallback<S,S>(getConverter(), EntityProjection.nonProjecting(entityClass), collectionName)) {
@Override
public Object doWith(Document object) {
ids.add(object.get("_id"));
return super.doWith(object);
}
};
List<T> result = doFind(collectionName, createDelegate(query), query.getQueryObject(), query.getFieldsObject(), entityClass,
new QueryCursorPreparer(query, entityClass), callback);
if (!CollectionUtils.isEmpty(result)) {
Query byIdInQuery = operations.getByIdInQuery(result);
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()) {
byIdInQuery.withReadPreference(query.getReadPreference());
removeQuery.withReadPreference(query.getReadPreference());
}
remove(byIdInQuery, entityClass, collectionName);
remove(removeQuery, entityClass, collectionName);
}
return result;
@ -2819,9 +2863,9 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, @@ -2819,9 +2863,9 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
}
@SuppressWarnings("ConstantConditions")
protected <T> @Nullable T doFindAndModify(CollectionPreparer collectionPreparer, String collectionName,
Document query, @Nullable Document fields, @Nullable Document sort, Class<T> entityClass, UpdateDefinition update,
@Nullable FindAndModifyOptions options) {
protected <S, T> @Nullable T doFindAndModify(CollectionPreparer collectionPreparer, String collectionName,
Document query, @Nullable Document fields, @Nullable Document sort, Class<S> entityClass, UpdateDefinition update,
@Nullable FindAndModifyOptions options, QueryResultConverter<? super S, ? extends T> resultConverter) {
if (options == null) {
options = new FindAndModifyOptions();
@ -2843,10 +2887,12 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, @@ -2843,10 +2887,12 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
serializeToJsonSafely(mappedUpdate), collectionName));
}
DocumentCallback<T> callback = getResultReader(EntityProjection.nonProjecting(entityClass), 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);
}
/**
@ -2865,15 +2911,15 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, @@ -2865,15 +2911,15 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
* {@literal false} and {@link FindAndReplaceOptions#isUpsert() upsert} is {@literal false}.
*/
@Nullable
protected <T> T doFindAndReplace(CollectionPreparer collectionPreparer, String collectionName,
protected <S, T> T doFindAndReplace(CollectionPreparer collectionPreparer, String collectionName,
Document mappedQuery, Document mappedFields, Document mappedSort,
com.mongodb.client.model.@Nullable Collation collation, Class<?> entityType, Document replacement,
com.mongodb.client.model.@Nullable Collation collation, Class<S> entityType, Document replacement,
FindAndReplaceOptions options, Class<T> resultType) {
EntityProjection<T, ?> projection = operations.introspectProjection(resultType, entityType);
EntityProjection<T, S> projection = operations.introspectProjection(resultType, entityType);
return doFindAndReplace(collectionPreparer, collectionName, mappedQuery, mappedFields, mappedSort, collation,
entityType, replacement, options, projection);
entityType, replacement, options, projection, QueryResultConverter.entity());
}
CollectionPreparerDelegate createDelegate(Query query) {
@ -2908,10 +2954,10 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, @@ -2908,10 +2954,10 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
* @since 3.4
*/
@Nullable
private <T> T doFindAndReplace(CollectionPreparer collectionPreparer, String collectionName,
private <S, T, R> R doFindAndReplace(CollectionPreparer collectionPreparer, String collectionName,
Document mappedQuery, Document mappedFields, Document mappedSort,
com.mongodb.client.model.@Nullable Collation collation, Class<?> entityType, Document replacement,
FindAndReplaceOptions options, EntityProjection<T, ?> projection) {
com.mongodb.client.model.@Nullable Collation collation, Class<T> entityType, Document replacement,
FindAndReplaceOptions options, EntityProjection<S, T> projection, QueryResultConverter<? super S, ? extends R> resultConverter) {
if (LOGGER.isDebugEnabled()) {
LOGGER
@ -2922,8 +2968,9 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, @@ -2922,8 +2968,9 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
serializeToJsonSafely(mappedSort), entityType, serializeToJsonSafely(replacement), collectionName));
}
DocumentCallback<R> callback = getResultReader(projection, collectionName, resultConverter);
return executeFindOneInternal(new FindAndReplaceCallback(collectionPreparer, mappedQuery, mappedFields, mappedSort,
replacement, collation, options), new ProjectingReadCallback<>(mongoConverter, projection, collectionName),
replacement, collation, options),callback,
collectionName);
}
@ -3421,7 +3468,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, @@ -3421,7 +3468,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
}
}
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;

9
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ExecutableRemoveOperationSupportTests.java

@ -21,6 +21,7 @@ import static org.springframework.data.mongodb.core.query.Query.*; @@ -21,6 +21,7 @@ import static org.springframework.data.mongodb.core.query.Query.*;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@ -108,6 +109,14 @@ class ExecutableRemoveOperationSupportTests { @@ -108,6 +109,14 @@ class ExecutableRemoveOperationSupportTests {
assertThat(result).containsExactly(han);
}
@Test // GH-0
void removeAndReturnAllMatchingWithResultConverter() {
List<Optional<Person>> result = template.remove(Person.class).matching(query(where("firstname").is("han"))).map((raw, converted) -> Optional.of(converted.get())).findAndRemove();
assertThat(result).containsExactly(Optional.of(han));
}
@org.springframework.data.mongodb.core.mapping.Document(collection = STAR_WARS)
static class Person {

24
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ExecutableUpdateOperationSupportTests.java

@ -185,6 +185,17 @@ class ExecutableUpdateOperationSupportTests { @@ -185,6 +185,17 @@ class ExecutableUpdateOperationSupportTests {
assertThat(result.get()).isNotEqualTo(han).hasFieldOrPropertyWithValue("firstname", "Han");
}
@Test // GH-
void findAndModifyWithResultConverter() {
Optional<Person> result = template.update(Person.class).matching(queryHan())
.apply(new Update().set("firstname", "Han")).withOptions(FindAndModifyOptions.options().returnNew(true))
.map((raw, converted) -> Optional.of(converted.get()))
.findAndModifyValue();
assertThat(result.get()).isNotEqualTo(han).hasFieldOrPropertyWithValue("firstname", "Han");
}
@Test // DATAMONGO-1563
void upsert() {
@ -282,6 +293,19 @@ class ExecutableUpdateOperationSupportTests { @@ -282,6 +293,19 @@ class ExecutableUpdateOperationSupportTests {
assertThat(result.getName()).isEqualTo(han.firstname);
}
@Test // GH-
void findAndReplaceWithResultConverter() {
Person luke = new Person();
luke.firstname = "Luke";
Optional<Jedi> result = template.update(Person.class).matching(queryHan()).replaceWith(luke).as(Jedi.class) //
.mapResult((raw, converted) -> Optional.of(converted.get()))
.findAndReplaceValue();
assertThat(result.get()).isInstanceOf(Jedi.class).extracting(Jedi::getName).isEqualTo(han.firstname);
}
private Query queryHan() {
return query(where("id").is(han.getId()));
}

4
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateUnitTests.java

@ -437,8 +437,8 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests { @@ -437,8 +437,8 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
verify(collection, times(1)).deleteMany(queryCaptor.capture(), any());
Document idField = DocumentTestUtils.getAsDocument(queryCaptor.getValue(), "_id");
assertThat((List<Object>) idField.get("$in")).containsExactly(Integer.valueOf(0), Integer.valueOf(1));
List<Document> ors = DocumentTestUtils.getAsDBList(queryCaptor.getValue(), "$or");
assertThat(ors).containsExactlyInAnyOrder(new Document("_id", 0), new Document("_id", 1));
}
@Test // DATAMONGO-566

89
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/QueryResultConverterUnitTests.java

@ -0,0 +1,89 @@ @@ -0,0 +1,89 @@
/*
* Copyright 2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core;
import static org.assertj.core.api.Assertions.assertThat;
import org.bson.Document;
import org.junit.jupiter.api.Test;
import org.springframework.data.mongodb.core.QueryResultConverter.ConversionResultSupplier;
/**
* @author Christoph Strobl
*/
class QueryResultConverterUnitTests {
public static final ConversionResultSupplier<Document> ERROR_SUPPLIER = () -> {
throw new IllegalStateException("must not read conversion result");
};
@Test // GH-
void converterDoesNotEagerlyRetrieveConversionResultFromSupplier() {
QueryResultConverter<Document, String> converter = new QueryResultConverter<Document, String>() {
@Override
public String mapDocument(Document document, ConversionResultSupplier<Document> reader) {
return "done";
}
};
assertThat(converter.mapDocument(new Document(), ERROR_SUPPLIER)).isEqualTo("done");
}
@Test // GH-
void converterPassesOnConversionResultToNextStage() {
Document source = new Document("value", "10");
QueryResultConverter<Document, Integer> stagedConverter = new QueryResultConverter<Document, String>() {
@Override
public String mapDocument(Document document, ConversionResultSupplier<Document> reader) {
return document.get("value", "-1");
}
}.andThen(new QueryResultConverter<String, Integer>() {
@Override
public Integer mapDocument(Document document, ConversionResultSupplier<String> reader) {
assertThat(document).isEqualTo(source);
return Integer.valueOf(reader.get());
}
});
assertThat(stagedConverter.mapDocument(source, ERROR_SUPPLIER)).isEqualTo(10);
}
@Test // GH-
void entityConverterDelaysConversion() {
Document source = new Document("value", "10");
QueryResultConverter<Document, Integer> converter = QueryResultConverter.<Document> entity()
.andThen(new QueryResultConverter<Document, Integer>() {
@Override
public Integer mapDocument(Document document, ConversionResultSupplier<Document> reader) {
assertThat(document).isEqualTo(source);
return Integer.valueOf(document.get("value", "20"));
}
});
assertThat(converter.mapDocument(source, ERROR_SUPPLIER)).isEqualTo(10);
}
}
Loading…
Cancel
Save