Support QueryResultConverter for delete, replace and update operations.
Original Pull Request: #4949
This commit is contained in:
committed by
Mark Paluch
parent
ed7943a949
commit
ec49c14bb9
@@ -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 {
|
||||
*/
|
||||
<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 {
|
||||
* @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 {
|
||||
RemoveWithQuery<T> inCollection(String collection);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
* @since 2.0
|
||||
|
||||
@@ -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 {
|
||||
|
||||
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 {
|
||||
|
||||
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 {
|
||||
|
||||
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() {
|
||||
|
||||
@@ -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;
|
||||
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 {
|
||||
*/
|
||||
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 {
|
||||
*/
|
||||
@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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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 {
|
||||
* @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 {
|
||||
this.findAndReplaceOptions = findAndReplaceOptions;
|
||||
this.replacement = replacement;
|
||||
this.targetType = targetType;
|
||||
this.resultConverter = resultConverter;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -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 {
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
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
|
||||
|
||||
@@ -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;
|
||||
@@ -1143,7 +1144,13 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
@Nullable
|
||||
@Override
|
||||
public <T> T findAndModify(Query query, UpdateDefinition update, FindAndModifyOptions options,
|
||||
Class<T> entityClass, String collectionName) {
|
||||
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,
|
||||
}
|
||||
|
||||
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) {
|
||||
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,
|
||||
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,
|
||||
*/
|
||||
@SuppressWarnings("NullAway")
|
||||
protected <T> List<T> doFindAndDelete(String collectionName, Query query, Class<T> entityClass) {
|
||||
return doFindAndDelete(collectionName, query, entityClass, QueryResultConverter.entity());
|
||||
}
|
||||
|
||||
List<T> result = find(query, entityClass, collectionName);
|
||||
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);
|
||||
|
||||
// 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,
|
||||
}
|
||||
|
||||
@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,
|
||||
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,
|
||||
* {@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,
|
||||
* @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,
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
@@ -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 {
|
||||
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 {
|
||||
|
||||
|
||||
@@ -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 {
|
||||
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()));
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user