DATAMONGO-1827 - Add support for findAndReplace.
We now support findAndReplace operations through the imperative and reactive Template API to find an object by a query and entirely replace it (except for the _id).
template.findAndReplace(query(where("name").is("Han")), new Person("Luke"))
template.update(Person.class).inCollection(STAR_WARS).matching(query(where("name").is("Han"))).replaceWith(luke).findAndReplace()
Original Pull Request: #569
This commit is contained in:
committed by
Christoph Strobl
parent
56e61a2965
commit
fa880f1c5c
@@ -19,12 +19,12 @@ import java.util.Optional;
|
||||
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
import org.springframework.data.mongodb.core.query.Update;
|
||||
|
||||
import com.mongodb.client.result.UpdateResult;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
import com.mongodb.client.result.UpdateResult;
|
||||
|
||||
/**
|
||||
* {@link ExecutableUpdateOperation} allows creation and execution of MongoDB update / findAndModify operations in a
|
||||
* {@link ExecutableUpdateOperation} allows creation and execution of MongoDB update / findAndModify / findAndReplace operations in a
|
||||
* fluent API style. <br />
|
||||
* The starting {@literal domainType} is used for mapping the {@link Query} provided via {@code matching}, as well as
|
||||
* the {@link Update} via {@code apply} into the MongoDB specific representations. The collection to operate on is by
|
||||
@@ -73,6 +73,16 @@ public interface ExecutableUpdateOperation {
|
||||
* @throws IllegalArgumentException if update is {@literal null}.
|
||||
*/
|
||||
TerminatingUpdate<T> apply(Update update);
|
||||
|
||||
/**
|
||||
* Specify {@code replacement} object.
|
||||
*
|
||||
* @param replacement must not be {@literal null}.
|
||||
* @return new instance of {@link FindAndReplaceOptions}.
|
||||
* @throws IllegalArgumentException if options is {@literal null}.
|
||||
* 2.1
|
||||
*/
|
||||
FindAndReplaceWithOptions<T> replaceWith(T replacement);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -153,6 +163,47 @@ public interface ExecutableUpdateOperation {
|
||||
T findAndModifyValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Define {@link FindAndReplaceOptions}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 2.1
|
||||
*/
|
||||
interface FindAndReplaceWithOptions<T> extends TerminatingFindAndReplace<T> {
|
||||
|
||||
/**
|
||||
* Explicitly define {@link FindAndReplaceOptions} for the {@link Update}.
|
||||
*
|
||||
* @param options must not be {@literal null}.
|
||||
* @return new instance of {@link FindAndReplaceOptions}.
|
||||
* @throws IllegalArgumentException if options is {@literal null}.
|
||||
*/
|
||||
TerminatingFindAndReplace<T> withOptions(FindAndReplaceOptions options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger findAndReplace execution by calling one of the terminating methods.
|
||||
*/
|
||||
interface TerminatingFindAndReplace<T> {
|
||||
|
||||
/**
|
||||
* Find, replace and return the first matching document.
|
||||
*
|
||||
* @return {@link Optional#empty()} if nothing found.
|
||||
*/
|
||||
default Optional<T> findAndReplace() {
|
||||
return Optional.ofNullable(findAndReplaceValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* Find, replace and return the first matching document.
|
||||
*
|
||||
* @return {@literal null} if nothing found.
|
||||
*/
|
||||
@Nullable
|
||||
T findAndReplaceValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger update execution by calling one of the terminating methods.
|
||||
*
|
||||
|
||||
@@ -51,7 +51,7 @@ class ExecutableUpdateOperationSupport implements ExecutableUpdateOperation {
|
||||
|
||||
Assert.notNull(domainType, "DomainType must not be null!");
|
||||
|
||||
return new ExecutableUpdateSupport<>(template, domainType, ALL_QUERY, null, null, null);
|
||||
return new ExecutableUpdateSupport<>(template, domainType, ALL_QUERY, null, null, null, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -60,15 +60,17 @@ class ExecutableUpdateOperationSupport implements ExecutableUpdateOperation {
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
|
||||
static class ExecutableUpdateSupport<T>
|
||||
implements ExecutableUpdate<T>, UpdateWithCollection<T>, UpdateWithQuery<T>, TerminatingUpdate<T> {
|
||||
static class ExecutableUpdateSupport<T> implements ExecutableUpdate<T>, UpdateWithCollection<T>, UpdateWithQuery<T>,
|
||||
TerminatingUpdate<T>, FindAndReplaceWithOptions<T>, TerminatingFindAndReplace<T> {
|
||||
|
||||
@NonNull MongoTemplate template;
|
||||
@NonNull Class<T> domainType;
|
||||
Query query;
|
||||
@Nullable Update update;
|
||||
@Nullable String collection;
|
||||
@Nullable FindAndModifyOptions options;
|
||||
@Nullable FindAndModifyOptions findAndModifyOptions;
|
||||
@Nullable FindAndReplaceOptions findAndReplaceOptions;
|
||||
@Nullable T replacement;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
@@ -79,7 +81,8 @@ class ExecutableUpdateOperationSupport implements ExecutableUpdateOperation {
|
||||
|
||||
Assert.notNull(update, "Update must not be null!");
|
||||
|
||||
return new ExecutableUpdateSupport<>(template, domainType, query, update, collection, options);
|
||||
return new ExecutableUpdateSupport<>(template, domainType, query, update, collection, findAndModifyOptions,
|
||||
findAndReplaceOptions, replacement);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -91,7 +94,8 @@ class ExecutableUpdateOperationSupport implements ExecutableUpdateOperation {
|
||||
|
||||
Assert.hasText(collection, "Collection must not be null nor empty!");
|
||||
|
||||
return new ExecutableUpdateSupport<>(template, domainType, query, update, collection, options);
|
||||
return new ExecutableUpdateSupport<>(template, domainType, query, update, collection, findAndModifyOptions,
|
||||
findAndReplaceOptions, replacement);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -103,7 +107,34 @@ class ExecutableUpdateOperationSupport implements ExecutableUpdateOperation {
|
||||
|
||||
Assert.notNull(options, "Options must not be null!");
|
||||
|
||||
return new ExecutableUpdateSupport<>(template, domainType, query, update, collection, options);
|
||||
return new ExecutableUpdateSupport<>(template, domainType, query, update, collection, options,
|
||||
findAndReplaceOptions, replacement);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.ExecutableUpdateOperation.UpdateWithUpdate#replaceWith(Object)
|
||||
*/
|
||||
@Override
|
||||
public FindAndReplaceWithOptions<T> replaceWith(T replacement) {
|
||||
|
||||
Assert.notNull(replacement, "Replacement must not be null!");
|
||||
|
||||
return new ExecutableUpdateSupport<>(template, domainType, query, update, collection, findAndModifyOptions,
|
||||
findAndReplaceOptions, replacement);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.ExecutableUpdateOperation.FindAndReplaceWithOptions#withOptions(org.springframework.data.mongodb.core.FindAndReplaceOptions)
|
||||
*/
|
||||
@Override
|
||||
public TerminatingFindAndReplace<T> withOptions(FindAndReplaceOptions options) {
|
||||
|
||||
Assert.notNull(options, "Options must not be null!");
|
||||
|
||||
return new ExecutableUpdateSupport<>(template, domainType, query, update, collection, findAndModifyOptions,
|
||||
options, replacement);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -115,7 +146,8 @@ class ExecutableUpdateOperationSupport implements ExecutableUpdateOperation {
|
||||
|
||||
Assert.notNull(query, "Query must not be null!");
|
||||
|
||||
return new ExecutableUpdateSupport<>(template, domainType, query, update, collection, options);
|
||||
return new ExecutableUpdateSupport<>(template, domainType, query, update, collection, findAndModifyOptions,
|
||||
findAndReplaceOptions, replacement);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -151,7 +183,20 @@ class ExecutableUpdateOperationSupport implements ExecutableUpdateOperation {
|
||||
*/
|
||||
@Override
|
||||
public @Nullable T findAndModifyValue() {
|
||||
return template.findAndModify(query, update, options != null ? options : new FindAndModifyOptions(), domainType, getCollectionName());
|
||||
return template.findAndModify(query, update,
|
||||
findAndModifyOptions != null ? findAndModifyOptions : new FindAndModifyOptions(), domainType,
|
||||
getCollectionName());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.ExecutableUpdateOperation.TerminatingFindAndReplace#findAndReplaceValue()
|
||||
*/
|
||||
@Override
|
||||
public @Nullable T findAndReplaceValue() {
|
||||
return template.findAndReplace(query, replacement,
|
||||
findAndReplaceOptions != null ? findAndReplaceOptions : new FindAndReplaceOptions(), domainType,
|
||||
getCollectionName());
|
||||
}
|
||||
|
||||
private UpdateResult doUpdate(boolean multi, boolean upsert) {
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright 2018 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
|
||||
*
|
||||
* http://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 java.util.Optional;
|
||||
|
||||
import org.springframework.data.mongodb.core.query.Collation;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Options for
|
||||
* <a href="https://docs.mongodb.com/manual/reference/method/db.collection.findOneAndReplace/">findOneAndReplace<a/>.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 2.1
|
||||
*/
|
||||
public class FindAndReplaceOptions {
|
||||
|
||||
private boolean returnNew;
|
||||
private boolean upsert;
|
||||
|
||||
private @Nullable Collation collation;
|
||||
|
||||
/**
|
||||
* Static factory method to create a {@link FindAndReplaceOptions} instance.
|
||||
*
|
||||
* @return a new instance
|
||||
*/
|
||||
public static FindAndReplaceOptions options() {
|
||||
return new FindAndReplaceOptions();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param options
|
||||
* @return
|
||||
*/
|
||||
public static FindAndReplaceOptions of(@Nullable FindAndReplaceOptions source) {
|
||||
|
||||
FindAndReplaceOptions options = new FindAndReplaceOptions();
|
||||
|
||||
if (source == null) {
|
||||
return options;
|
||||
}
|
||||
|
||||
options.returnNew = source.returnNew;
|
||||
options.upsert = source.upsert;
|
||||
options.collation = source.collation;
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
public FindAndReplaceOptions returnNew(boolean returnNew) {
|
||||
this.returnNew = returnNew;
|
||||
return this;
|
||||
}
|
||||
|
||||
public FindAndReplaceOptions upsert(boolean upsert) {
|
||||
this.upsert = upsert;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the {@link Collation} specifying language-specific rules for string comparison.
|
||||
*
|
||||
* @param collation
|
||||
* @return
|
||||
*/
|
||||
public FindAndReplaceOptions collation(@Nullable Collation collation) {
|
||||
|
||||
this.collation = collation;
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean isReturnNew() {
|
||||
return returnNew;
|
||||
}
|
||||
|
||||
public boolean isUpsert() {
|
||||
return upsert;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link Collation} specifying language-specific rules for string comparison.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Optional<Collation> getCollation() {
|
||||
return Optional.ofNullable(collation);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -894,6 +894,98 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
<T> T findAndModify(Query query, Update update, FindAndModifyOptions options, Class<T> entityClass,
|
||||
String collectionName);
|
||||
|
||||
/**
|
||||
* Triggers
|
||||
* <a href="https://docs.mongodb.com/manual/reference/method/db.collection.findOneAndReplace/">findOneAndReplace<a/>
|
||||
* to replace a single document matching {@link Criteria} of given {@link Query} with the {@code replacement}
|
||||
* document.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
|
||||
* fields specification. Must not be {@literal null}.
|
||||
* @param replacement the replacement document. Must not be {@literal null}.
|
||||
* @return the converted object that was updated or {@literal null}, if not found.
|
||||
* @since 2.1
|
||||
*/
|
||||
@Nullable
|
||||
default <T> T findAndReplace(Query query, T replacement) {
|
||||
return findAndReplace(query, replacement, FindAndReplaceOptions.options());
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers
|
||||
* <a href="https://docs.mongodb.com/manual/reference/method/db.collection.findOneAndReplace/">findOneAndReplace<a/>
|
||||
* to replace a single document matching {@link Criteria} of given {@link Query} with the {@code replacement}
|
||||
* document.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
|
||||
* fields specification. Must not be {@literal null}.
|
||||
* @param replacement the replacement document. Must not be {@literal null}.
|
||||
* @param collectionName the collection to query. Must not be {@literal null}.
|
||||
* @return the converted object that was updated or {@literal null}, if not found.
|
||||
* @since 2.1
|
||||
*/
|
||||
@Nullable
|
||||
default <T> T findAndReplace(Query query, T replacement, String collectionName) {
|
||||
return findAndReplace(query, replacement, FindAndReplaceOptions.options(), collectionName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers
|
||||
* <a href="https://docs.mongodb.com/manual/reference/method/db.collection.findOneAndReplace/">findOneAndReplace<a/>
|
||||
* to replace a single document matching {@link Criteria} of given {@link Query} with the {@code replacement} document
|
||||
* taking {@link FindAndReplaceOptions} into account.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
|
||||
* fields specification. Must not be {@literal null}.
|
||||
* @param replacement the replacement document. Must not be {@literal null}.
|
||||
* @param options the {@link FindAndModifyOptions} holding additional information. Must not be {@literal null}.
|
||||
* @return the converted object that was updated or {@literal null}, if not found. Depending on the value of
|
||||
* {@link FindAndReplaceOptions#isReturnNew()} this will either be the object as it was before the update or
|
||||
* as it is after the update.
|
||||
* @since 2.1
|
||||
*/
|
||||
@Nullable
|
||||
<T> T findAndReplace(Query query, T replacement, FindAndReplaceOptions options);
|
||||
|
||||
/**
|
||||
* Triggers
|
||||
* <a href="https://docs.mongodb.com/manual/reference/method/db.collection.findOneAndReplace/">findOneAndReplace<a/>
|
||||
* to replace a single document matching {@link Criteria} of given {@link Query} with the {@code replacement} document
|
||||
* taking {@link FindAndReplaceOptions} into account.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
|
||||
* fields specification. Must not be {@literal null}.
|
||||
* @param replacement the replacement document. Must not be {@literal null}.
|
||||
* @param options the {@link FindAndModifyOptions} holding additional information. Must not be {@literal null}.
|
||||
* @return the converted object that was updated or {@literal null}, if not found. Depending on the value of
|
||||
* {@link FindAndReplaceOptions#isReturnNew()} this will either be the object as it was before the update or
|
||||
* as it is after the update.
|
||||
* @since 2.1
|
||||
*/
|
||||
@Nullable
|
||||
<T> T findAndReplace(Query query, T replacement, FindAndReplaceOptions options, String collectionName);
|
||||
|
||||
/**
|
||||
* Triggers
|
||||
* <a href="https://docs.mongodb.com/manual/reference/method/db.collection.findOneAndReplace/">findOneAndReplace<a/>
|
||||
* to replace a single document matching {@link Criteria} of given {@link Query} with the {@code replacement} document
|
||||
* taking {@link FindAndReplaceOptions} into account.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
|
||||
* fields specification. Must not be {@literal null}.
|
||||
* @param replacement the replacement document. Must not be {@literal null}.
|
||||
* @param options the {@link FindAndModifyOptions} holding additional information. Must not be {@literal null}.
|
||||
* @param entityClass the parametrized type. Must not be {@literal null}.
|
||||
* @param collectionName the collection to query. Must not be {@literal null}.
|
||||
* @return the converted object that was updated or {@literal null}, if not found. Depending on the value of
|
||||
* {@link FindAndReplaceOptions#isReturnNew()} this will either be the object as it was before the update or
|
||||
* as it is after the update.
|
||||
* @since 2.1
|
||||
*/
|
||||
@Nullable
|
||||
<T> T findAndReplace(Query query, T replacement, FindAndReplaceOptions options, Class<T> entityClass,
|
||||
String collectionName);
|
||||
|
||||
/**
|
||||
* Map the results of an ad-hoc query on the collection for the entity type to a single instance of an object of the
|
||||
* specified type. The first document that matches the query is returned and also removed from the collection in the
|
||||
|
||||
@@ -1070,6 +1070,64 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
getMappedSortObject(query, entityClass), entityClass, update, optionsToUse);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.MongoOperations#findAndReplace(org.springframework.data.mongodb.core.query.Query, java.lang.Object, org.springframework.data.mongodb.core.FindAndReplaceOptions)
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
public <T> T findAndReplace(Query query, T replacement, FindAndReplaceOptions options) {
|
||||
|
||||
Assert.notNull(replacement, "Replacement must not be null!");
|
||||
|
||||
Class<T> entityClass = (Class) ClassUtils.getUserClass(replacement);
|
||||
String collectionName = determineCollectionName(entityClass);
|
||||
|
||||
return findAndReplace(query, replacement, options, collectionName);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.MongoOperations#findAndReplace(org.springframework.data.mongodb.core.query.Query, java.lang.Object, org.springframework.data.mongodb.core.FindAndReplaceOptions, java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
public <T> T findAndReplace(Query query, T replacement, FindAndReplaceOptions options, String collectionName) {
|
||||
|
||||
Assert.notNull(replacement, "Replacement must not be null!");
|
||||
|
||||
Class<T> entityClass = (Class) ClassUtils.getUserClass(replacement);
|
||||
|
||||
return findAndReplace(query, replacement, options, entityClass, collectionName);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.MongoOperations#findAndReplace(org.springframework.data.mongodb.core.query.Query, java.lang.Object, org.springframework.data.mongodb.core.FindAndReplaceOptions, java.lang.Class, java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public <T> T findAndReplace(Query query, T replacement, FindAndReplaceOptions options, Class<T> entityClass,
|
||||
String collectionName) {
|
||||
|
||||
Assert.notNull(query, "Query must not be null!");
|
||||
Assert.notNull(replacement, "Replacement must not be null!");
|
||||
Assert.notNull(options, "Options must not be null!");
|
||||
Assert.notNull(entityClass, "Entity class must not be null!");
|
||||
Assert.notNull(collectionName, "CollectionName must not be null!");
|
||||
|
||||
FindAndReplaceOptions optionsToUse = FindAndReplaceOptions.of(options);
|
||||
|
||||
Optionals.ifAllPresent(query.getCollation(), optionsToUse.getCollation(), (l, r) -> {
|
||||
throw new IllegalArgumentException(
|
||||
"Both Query and FindAndReplaceOptions define a collation. Please provide the collation only via one of the two.");
|
||||
});
|
||||
|
||||
query.getCollation().ifPresent(optionsToUse::collation);
|
||||
|
||||
return doFindAndReplace(collectionName, query.getQueryObject(), query.getFieldsObject(), query.getSortObject(),
|
||||
entityClass, replacement, options);
|
||||
}
|
||||
|
||||
// Find methods that take a Query to express the query and that return a single object that is also removed from the
|
||||
// collection in the database.
|
||||
|
||||
@@ -2603,6 +2661,32 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
new ReadDocumentCallback<T>(readerToUse, entityClass, collectionName), collectionName);
|
||||
}
|
||||
|
||||
protected <T> T doFindAndReplace(String collectionName, Document query, Document fields, Document sort,
|
||||
Class<T> entityClass, Object replacement, @Nullable FindAndReplaceOptions options) {
|
||||
|
||||
EntityReader<? super T, Bson> readerToUse = this.mongoConverter;
|
||||
|
||||
if (options == null) {
|
||||
options = new FindAndReplaceOptions();
|
||||
}
|
||||
|
||||
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(entityClass);
|
||||
|
||||
Document mappedQuery = queryMapper.getMappedObject(query, entity);
|
||||
Document dbDoc = toDocument(replacement, this.mongoConverter);
|
||||
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug(
|
||||
"findAndReplace using query: {} fields: {} sort: {} for class: {} and replacement: {} " + "in collection: {}",
|
||||
serializeToJsonSafely(mappedQuery), fields, sort, entityClass, serializeToJsonSafely(dbDoc), collectionName);
|
||||
}
|
||||
|
||||
maybeEmitEvent(new BeforeSaveEvent<>(replacement, dbDoc, collectionName));
|
||||
|
||||
return executeFindOneInternal(new FindAndReplaceCallback(mappedQuery, fields, sort, dbDoc, options),
|
||||
new ReadDocumentCallback<T>(readerToUse, entityClass, collectionName), collectionName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates the id property of the saved object, if it's not set already.
|
||||
*
|
||||
@@ -3018,6 +3102,41 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
}
|
||||
}
|
||||
|
||||
private static class FindAndReplaceCallback implements CollectionCallback<Document> {
|
||||
|
||||
private final Document query;
|
||||
private final Document fields;
|
||||
private final Document sort;
|
||||
private final Document update;
|
||||
private final FindAndReplaceOptions options;
|
||||
|
||||
public FindAndReplaceCallback(Document query, Document fields, Document sort, Document update,
|
||||
FindAndReplaceOptions options) {
|
||||
this.query = query;
|
||||
this.fields = fields;
|
||||
this.sort = sort;
|
||||
this.update = update;
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
public Document doInCollection(MongoCollection<Document> collection) throws MongoException, DataAccessException {
|
||||
|
||||
FindOneAndReplaceOptions opts = new FindOneAndReplaceOptions();
|
||||
opts.sort(sort);
|
||||
if (options.isUpsert()) {
|
||||
opts.upsert(true);
|
||||
}
|
||||
opts.projection(fields);
|
||||
if (options.isReturnNew()) {
|
||||
opts.returnDocument(ReturnDocument.AFTER);
|
||||
}
|
||||
|
||||
options.getCollation().map(Collation::toMongoCollation).ifPresent(opts::collation);
|
||||
|
||||
return collection.findOneAndReplace(query, update, opts);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple internal callback to allow operations on a {@link Document}.
|
||||
*
|
||||
|
||||
@@ -688,6 +688,93 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
<T> Mono<T> findAndModify(Query query, Update update, FindAndModifyOptions options, Class<T> entityClass,
|
||||
String collectionName);
|
||||
|
||||
/**
|
||||
* Triggers
|
||||
* <a href="https://docs.mongodb.com/manual/reference/method/db.collection.findOneAndReplace/">findOneAndReplace<a/>
|
||||
* to replace a single document matching {@link Criteria} of given {@link Query} with the {@code replacement}
|
||||
* document.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
|
||||
* fields specification. Must not be {@literal null}.
|
||||
* @param replacement the replacement document. Must not be {@literal null}.
|
||||
* @return the converted object that was updated or {@link Mono#empty()}, if not found.
|
||||
* @since 2.1
|
||||
*/
|
||||
default <T> Mono<T> findAndReplace(Query query, T replacement) {
|
||||
return findAndReplace(query, replacement, FindAndReplaceOptions.options());
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers
|
||||
* <a href="https://docs.mongodb.com/manual/reference/method/db.collection.findOneAndReplace/">findOneAndReplace<a/>
|
||||
* to replace a single document matching {@link Criteria} of given {@link Query} with the {@code replacement}
|
||||
* document.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
|
||||
* fields specification. Must not be {@literal null}.
|
||||
* @param replacement the replacement document. Must not be {@literal null}.
|
||||
* @param collectionName the collection to query. Must not be {@literal null}.
|
||||
* @return the converted object that was updated or {@link Mono#empty()}, if not found.
|
||||
* @since 2.1
|
||||
*/
|
||||
default <T> Mono<T> findAndReplace(Query query, T replacement, String collectionName) {
|
||||
return findAndReplace(query, replacement, FindAndReplaceOptions.options(), collectionName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers
|
||||
* <a href="https://docs.mongodb.com/manual/reference/method/db.collection.findOneAndReplace/">findOneAndReplace<a/>
|
||||
* to replace a single document matching {@link Criteria} of given {@link Query} with the {@code replacement} document
|
||||
* taking {@link FindAndReplaceOptions} into account.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
|
||||
* fields specification. Must not be {@literal null}.
|
||||
* @param replacement the replacement document. Must not be {@literal null}.
|
||||
* @param options the {@link FindAndModifyOptions} holding additional information. Must not be {@literal null}.
|
||||
* @return the converted object that was updated or {@link Mono#empty()}, if not found. Depending on the value of
|
||||
* {@link FindAndReplaceOptions#isReturnNew()} this will either be the object as it was before the update or
|
||||
* as it is after the update.
|
||||
* @since 2.1
|
||||
*/
|
||||
<T> Mono<T> findAndReplace(Query query, T replacement, FindAndReplaceOptions options);
|
||||
|
||||
/**
|
||||
* Triggers
|
||||
* <a href="https://docs.mongodb.com/manual/reference/method/db.collection.findOneAndReplace/">findOneAndReplace<a/>
|
||||
* to replace a single document matching {@link Criteria} of given {@link Query} with the {@code replacement} document
|
||||
* taking {@link FindAndReplaceOptions} into account.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
|
||||
* fields specification. Must not be {@literal null}.
|
||||
* @param replacement the replacement document. Must not be {@literal null}.
|
||||
* @param options the {@link FindAndModifyOptions} holding additional information. Must not be {@literal null}.
|
||||
* @return the converted object that was updated or {@link Mono#empty()}, if not found. Depending on the value of
|
||||
* {@link FindAndReplaceOptions#isReturnNew()} this will either be the object as it was before the update or
|
||||
* as it is after the update.
|
||||
* @since 2.1
|
||||
*/
|
||||
<T> Mono<T> findAndReplace(Query query, T replacement, FindAndReplaceOptions options, String collectionName);
|
||||
|
||||
/**
|
||||
* Triggers
|
||||
* <a href="https://docs.mongodb.com/manual/reference/method/db.collection.findOneAndReplace/">findOneAndReplace<a/>
|
||||
* to replace a single document matching {@link Criteria} of given {@link Query} with the {@code replacement} document
|
||||
* taking {@link FindAndReplaceOptions} into account.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
|
||||
* fields specification. Must not be {@literal null}.
|
||||
* @param replacement the replacement document. Must not be {@literal null}.
|
||||
* @param options the {@link FindAndModifyOptions} holding additional information. Must not be {@literal null}.
|
||||
* @param entityClass the parametrized type. Must not be {@literal null}.
|
||||
* @param collectionName the collection to query. Must not be {@literal null}.
|
||||
* @return the converted object that was updated or {@link Mono#empty()}, if not found. Depending on the value of
|
||||
* {@link FindAndReplaceOptions#isReturnNew()} this will either be the object as it was before the update or
|
||||
* as it is after the update.
|
||||
* @since 2.1
|
||||
*/
|
||||
<T> Mono<T> findAndReplace(Query query, T replacement, FindAndReplaceOptions options, Class<T> entityClass,
|
||||
String collectionName);
|
||||
|
||||
/**
|
||||
* Map the results of an ad-hoc query on the collection for the entity type to a single instance of an object of the
|
||||
* specified type. The first document that matches the query is returned and also removed from the collection in the
|
||||
|
||||
@@ -129,6 +129,7 @@ import com.mongodb.client.model.CreateCollectionOptions;
|
||||
import com.mongodb.client.model.DeleteOptions;
|
||||
import com.mongodb.client.model.Filters;
|
||||
import com.mongodb.client.model.FindOneAndDeleteOptions;
|
||||
import com.mongodb.client.model.FindOneAndReplaceOptions;
|
||||
import com.mongodb.client.model.FindOneAndUpdateOptions;
|
||||
import com.mongodb.client.model.ReplaceOptions;
|
||||
import com.mongodb.client.model.ReturnDocument;
|
||||
@@ -1108,6 +1109,63 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
getMappedSortObject(query, entityClass), entityClass, update, optionsToUse);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.ReactiveMongoOperations#findAndReplace(org.springframework.data.mongodb.core.query.Query, java.lang.Object, org.springframework.data.mongodb.core.FindAndReplaceOptions)
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> Mono<T> findAndReplace(Query query, T replacement, FindAndReplaceOptions options) {
|
||||
|
||||
Assert.notNull(replacement, "Replacement must not be null!");
|
||||
|
||||
Class<T> entityClass = (Class) ClassUtils.getUserClass(replacement);
|
||||
String collectionName = determineCollectionName(entityClass);
|
||||
|
||||
return findAndReplace(query, replacement, options, collectionName);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.ReactiveMongoOperations#findAndReplace(org.springframework.data.mongodb.core.query.Query, java.lang.Object, org.springframework.data.mongodb.core.FindAndReplaceOptions, java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public <T> Mono<T> findAndReplace(Query query, T replacement, FindAndReplaceOptions options, String collectionName) {
|
||||
|
||||
Assert.notNull(replacement, "Replacement must not be null!");
|
||||
|
||||
Class<T> entityClass = (Class) ClassUtils.getUserClass(replacement);
|
||||
|
||||
return findAndReplace(query, replacement, options, entityClass, collectionName);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.ReactiveMongoOperations#findAndReplace(org.springframework.data.mongodb.core.query.Query, java.lang.Object, org.springframework.data.mongodb.core.FindAndReplaceOptions, java.lang.Class, java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public <T> Mono<T> findAndReplace(Query query, T replacement, FindAndReplaceOptions options, Class<T> entityClass,
|
||||
String collectionName) {
|
||||
|
||||
Assert.notNull(query, "Query must not be null!");
|
||||
Assert.notNull(replacement, "Replacement must not be null!");
|
||||
Assert.notNull(options, "Options must not be null!");
|
||||
Assert.notNull(entityClass, "Entity class must not be null!");
|
||||
Assert.notNull(collectionName, "CollectionName must not be null!");
|
||||
|
||||
FindAndReplaceOptions optionsToUse = FindAndReplaceOptions.of(options);
|
||||
|
||||
Optionals.ifAllPresent(query.getCollation(), optionsToUse.getCollation(), (l, r) -> {
|
||||
throw new IllegalArgumentException(
|
||||
"Both Query and FindAndReplaceOptions define a collation. Please provide the collation only via one of the two.");
|
||||
});
|
||||
|
||||
query.getCollation().ifPresent(optionsToUse::collation);
|
||||
|
||||
return doFindAndReplace(collectionName, query.getQueryObject(), query.getFieldsObject(), query.getSortObject(),
|
||||
entityClass, replacement, options);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.ReactiveMongoOperations#findAndRemove(org.springframework.data.mongodb.core.query.Query, java.lang.Class)
|
||||
@@ -2453,6 +2511,31 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
});
|
||||
}
|
||||
|
||||
protected <T> Mono<T> doFindAndReplace(String collectionName, Document query, Document fields, Document sort,
|
||||
Class<T> entityClass, Object replacement, @Nullable FindAndReplaceOptions options) {
|
||||
|
||||
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(entityClass);
|
||||
|
||||
return Mono.defer(() -> {
|
||||
|
||||
Document mappedQuery = queryMapper.getMappedObject(query, entity);
|
||||
Document dbDoc = toDbObject(replacement, this.mongoConverter);
|
||||
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug(
|
||||
"findAndReplace using query: {} fields: {} sort: {} for class: {} and replacement: {} "
|
||||
+ "in collection: {}",
|
||||
serializeToJsonSafely(mappedQuery), fields, sort, entityClass, serializeToJsonSafely(dbDoc),
|
||||
collectionName);
|
||||
}
|
||||
|
||||
maybeEmitEvent(new BeforeSaveEvent<>(replacement, dbDoc, collectionName));
|
||||
|
||||
return executeFindOneInternal(new FindAndReplaceCallback(mappedQuery, fields, sort, dbDoc, options),
|
||||
new ReadDocumentCallback<T>(this.mongoConverter, entityClass, collectionName), collectionName);
|
||||
});
|
||||
}
|
||||
|
||||
protected <T> void maybeEmitEvent(MongoMappingEvent<T> event) {
|
||||
if (null != eventPublisher) {
|
||||
eventPublisher.publishEvent(event);
|
||||
@@ -2941,6 +3024,45 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
private static class FindAndReplaceCallback implements ReactiveCollectionCallback<Document> {
|
||||
|
||||
private final Document query;
|
||||
private final Document fields;
|
||||
private final Document sort;
|
||||
private final Document update;
|
||||
private final FindAndReplaceOptions options;
|
||||
|
||||
@Override
|
||||
public Publisher<Document> doInCollection(MongoCollection<Document> collection)
|
||||
throws MongoException, DataAccessException {
|
||||
|
||||
FindOneAndReplaceOptions findOneAndReplaceOptions = convertToFindOneAndReplaceOptions(options, fields, sort);
|
||||
return collection.findOneAndReplace(query, update, findOneAndReplaceOptions);
|
||||
}
|
||||
|
||||
private FindOneAndReplaceOptions convertToFindOneAndReplaceOptions(FindAndReplaceOptions options, Document fields,
|
||||
Document sort) {
|
||||
|
||||
FindOneAndReplaceOptions result = new FindOneAndReplaceOptions();
|
||||
|
||||
result = result.projection(fields).sort(sort).upsert(options.isUpsert());
|
||||
|
||||
if (options.isReturnNew()) {
|
||||
result = result.returnDocument(ReturnDocument.AFTER);
|
||||
} else {
|
||||
result = result.returnDocument(ReturnDocument.BEFORE);
|
||||
}
|
||||
|
||||
result = options.getCollation().map(Collation::toMongoCollation).map(result::collation).orElse(result);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private static FindOneAndDeleteOptions convertToFindOneAndDeleteOptions(Document fields, Document sort) {
|
||||
|
||||
FindOneAndDeleteOptions result = new FindOneAndDeleteOptions();
|
||||
|
||||
@@ -18,12 +18,13 @@ package org.springframework.data.mongodb.core;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
import org.springframework.data.mongodb.core.query.Update;
|
||||
|
||||
import com.mongodb.client.result.UpdateResult;
|
||||
|
||||
/**
|
||||
* {@link ReactiveUpdateOperation} allows creation and execution of reactive MongoDB update / findAndModify operations
|
||||
* in a fluent API style. <br />
|
||||
* {@link ReactiveUpdateOperation} allows creation and execution of reactive MongoDB update / findAndModify /
|
||||
* findAndReplace operations in a fluent API style. <br />
|
||||
* The starting {@literal domainType} is used for mapping the {@link Query} provided via {@code matching}, as well as
|
||||
* the {@link org.springframework.data.mongodb.core.query.Update} via {@code apply} into the MongoDB specific
|
||||
* representations. The collection to operate on is by default derived from the initial {@literal domainType} and can be
|
||||
@@ -68,6 +69,21 @@ public interface ReactiveUpdateOperation {
|
||||
Mono<T> findAndModify();
|
||||
}
|
||||
|
||||
/**
|
||||
* Compose findAndReplace execution by calling one of the terminating methods.
|
||||
*
|
||||
* @since 2.1
|
||||
*/
|
||||
interface TerminatingFindAndReplace<T> {
|
||||
|
||||
/**
|
||||
* Find, replace and return the first matching document.
|
||||
*
|
||||
* @return {@link Mono#empty()} if nothing found. Never {@literal null}.
|
||||
*/
|
||||
Mono<T> findAndReplace();
|
||||
}
|
||||
|
||||
/**
|
||||
* Compose update execution by calling one of the terminating methods.
|
||||
*/
|
||||
@@ -108,6 +124,16 @@ public interface ReactiveUpdateOperation {
|
||||
* @throws IllegalArgumentException if update is {@literal null}.
|
||||
*/
|
||||
TerminatingUpdate<T> apply(org.springframework.data.mongodb.core.query.Update update);
|
||||
|
||||
/**
|
||||
* Specify {@code replacement} object.
|
||||
*
|
||||
* @param replacement must not be {@literal null}.
|
||||
* @return new instance of {@link FindAndReplaceOptions}.
|
||||
* @throws IllegalArgumentException if options is {@literal null}.
|
||||
* @since 2.1
|
||||
*/
|
||||
FindAndReplaceWithOptions<T> replaceWith(T replacement);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -157,5 +183,23 @@ public interface ReactiveUpdateOperation {
|
||||
TerminatingFindAndModify<T> withOptions(FindAndModifyOptions options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Define {@link FindAndReplaceOptions}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 2.1
|
||||
*/
|
||||
interface FindAndReplaceWithOptions<T> extends TerminatingFindAndReplace<T> {
|
||||
|
||||
/**
|
||||
* Explicitly define {@link FindAndReplaceOptions} for the {@link Update}.
|
||||
*
|
||||
* @param options must not be {@literal null}.
|
||||
* @return new instance of {@link FindAndReplaceOptions}.
|
||||
* @throws IllegalArgumentException if options is {@literal null}.
|
||||
*/
|
||||
TerminatingFindAndReplace<T> withOptions(FindAndReplaceOptions options);
|
||||
}
|
||||
|
||||
interface ReactiveUpdate<T> extends UpdateWithCollection<T>, UpdateWithQuery<T>, UpdateWithUpdate<T> {}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import lombok.experimental.FieldDefaults;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
@@ -50,20 +51,22 @@ class ReactiveUpdateOperationSupport implements ReactiveUpdateOperation {
|
||||
|
||||
Assert.notNull(domainType, "DomainType must not be null!");
|
||||
|
||||
return new ReactiveUpdateSupport<>(template, domainType, ALL_QUERY, null, null, null);
|
||||
return new ReactiveUpdateSupport<>(template, domainType, ALL_QUERY, null, null, null, null, null);
|
||||
}
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
|
||||
static class ReactiveUpdateSupport<T>
|
||||
implements ReactiveUpdate<T>, UpdateWithCollection<T>, UpdateWithQuery<T>, TerminatingUpdate<T> {
|
||||
static class ReactiveUpdateSupport<T> implements ReactiveUpdate<T>, UpdateWithCollection<T>, UpdateWithQuery<T>,
|
||||
TerminatingUpdate<T>, FindAndReplaceWithOptions<T>, TerminatingFindAndReplace<T> {
|
||||
|
||||
@NonNull ReactiveMongoTemplate template;
|
||||
@NonNull Class<T> domainType;
|
||||
Query query;
|
||||
org.springframework.data.mongodb.core.query.Update update;
|
||||
String collection;
|
||||
FindAndModifyOptions options;
|
||||
@Nullable String collection;
|
||||
@Nullable FindAndModifyOptions findAndModifyOptions;
|
||||
@Nullable FindAndReplaceOptions findAndReplaceOptions;
|
||||
@Nullable T replacement;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
@@ -74,7 +77,8 @@ class ReactiveUpdateOperationSupport implements ReactiveUpdateOperation {
|
||||
|
||||
Assert.notNull(update, "Update must not be null!");
|
||||
|
||||
return new ReactiveUpdateSupport<>(template, domainType, query, update, collection, options);
|
||||
return new ReactiveUpdateSupport<>(template, domainType, query, update, collection, findAndModifyOptions,
|
||||
findAndReplaceOptions, replacement);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -86,7 +90,8 @@ class ReactiveUpdateOperationSupport implements ReactiveUpdateOperation {
|
||||
|
||||
Assert.hasText(collection, "Collection must not be null nor empty!");
|
||||
|
||||
return new ReactiveUpdateSupport<>(template, domainType, query, update, collection, options);
|
||||
return new ReactiveUpdateSupport<>(template, domainType, query, update, collection, findAndModifyOptions,
|
||||
findAndReplaceOptions, replacement);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -116,7 +121,18 @@ class ReactiveUpdateOperationSupport implements ReactiveUpdateOperation {
|
||||
|
||||
String collectionName = getCollectionName();
|
||||
|
||||
return template.findAndModify(query, update, options, domainType, collectionName);
|
||||
return template.findAndModify(query, update, findAndModifyOptions, domainType, collectionName);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.ReactiveUpdateOperation.TerminatingFindAndReplace#findAndReplace()
|
||||
*/
|
||||
@Override
|
||||
public Mono<T> findAndReplace() {
|
||||
return template.findAndReplace(query, replacement,
|
||||
findAndReplaceOptions != null ? findAndReplaceOptions : new FindAndReplaceOptions(), domainType,
|
||||
getCollectionName());
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -128,7 +144,8 @@ class ReactiveUpdateOperationSupport implements ReactiveUpdateOperation {
|
||||
|
||||
Assert.notNull(query, "Query must not be null!");
|
||||
|
||||
return new ReactiveUpdateSupport<>(template, domainType, query, update, collection, options);
|
||||
return new ReactiveUpdateSupport<>(template, domainType, query, update, collection, findAndModifyOptions,
|
||||
findAndReplaceOptions, replacement);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -149,7 +166,34 @@ class ReactiveUpdateOperationSupport implements ReactiveUpdateOperation {
|
||||
|
||||
Assert.notNull(options, "Options must not be null!");
|
||||
|
||||
return new ReactiveUpdateSupport<>(template, domainType, query, update, collection, options);
|
||||
return new ReactiveUpdateSupport<>(template, domainType, query, update, collection, options,
|
||||
findAndReplaceOptions, replacement);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.ReactiveUpdateOperation.UpdateWithUpdate#replaceWith(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public FindAndReplaceWithOptions<T> replaceWith(T replacement) {
|
||||
|
||||
Assert.notNull(replacement, "Replacement must not be null!");
|
||||
|
||||
return new ReactiveUpdateSupport<>(template, domainType, query, update, collection, findAndModifyOptions,
|
||||
findAndReplaceOptions, replacement);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.ReactiveUpdateOperation.FindAndReplaceWithOptions#withOptions(org.springframework.data.mongodb.core.FindAndReplaceOptions)
|
||||
*/
|
||||
@Override
|
||||
public TerminatingFindAndReplace<T> withOptions(FindAndReplaceOptions options) {
|
||||
|
||||
Assert.notNull(options, "Options must not be null!");
|
||||
|
||||
return new ReactiveUpdateSupport<>(template, domainType, query, update, collection, findAndModifyOptions, options,
|
||||
replacement);
|
||||
}
|
||||
|
||||
private Mono<UpdateResult> doUpdate(boolean multi, boolean upsert) {
|
||||
|
||||
@@ -179,6 +179,58 @@ public class ExecutableUpdateOperationSupportTests {
|
||||
assertThat(result.getUpsertedId()).isEqualTo(new BsonString("id-3"));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1827
|
||||
public void findAndReplaceValue() {
|
||||
|
||||
Person luke = new Person();
|
||||
luke.firstname = "Luke";
|
||||
|
||||
Person result = template.update(Person.class).matching(queryHan()).replaceWith(luke).findAndReplaceValue();
|
||||
|
||||
assertThat(result).isEqualTo(han);
|
||||
assertThat(template.findOne(queryHan(), Person.class)).isNotEqualTo(han).hasFieldOrPropertyWithValue("firstname",
|
||||
"Luke");
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1827
|
||||
public void findAndReplace() {
|
||||
|
||||
Person luke = new Person();
|
||||
luke.firstname = "Luke";
|
||||
|
||||
Optional<Person> result = template.update(Person.class).matching(queryHan()).replaceWith(luke).findAndReplace();
|
||||
|
||||
assertThat(result).contains(han);
|
||||
assertThat(template.findOne(queryHan(), Person.class)).isNotEqualTo(han).hasFieldOrPropertyWithValue("firstname",
|
||||
"Luke");
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1827
|
||||
public void findAndReplaceWithCollection() {
|
||||
|
||||
Person luke = new Person();
|
||||
luke.firstname = "Luke";
|
||||
|
||||
Optional<Person> result = template.update(Person.class).inCollection(STAR_WARS).matching(queryHan())
|
||||
.replaceWith(luke).findAndReplace();
|
||||
|
||||
assertThat(result).contains(han);
|
||||
assertThat(template.findOne(queryHan(), Person.class)).isNotEqualTo(han).hasFieldOrPropertyWithValue("firstname",
|
||||
"Luke");
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1827
|
||||
public void findAndReplaceWithOptions() {
|
||||
|
||||
Person luke = new Person();
|
||||
luke.firstname = "Luke";
|
||||
|
||||
Person result = template.update(Person.class).matching(queryHan()).replaceWith(luke)
|
||||
.withOptions(FindAndReplaceOptions.options().returnNew(true)).findAndReplaceValue();
|
||||
|
||||
assertThat(result).isNotEqualTo(han).hasFieldOrPropertyWithValue("firstname", "Luke");
|
||||
}
|
||||
|
||||
private Query queryHan() {
|
||||
return query(where("id").is(han.getId()));
|
||||
}
|
||||
|
||||
@@ -78,6 +78,7 @@ import org.springframework.data.mongodb.core.mapping.event.AbstractMongoEventLis
|
||||
import org.springframework.data.mongodb.core.mapping.event.BeforeConvertEvent;
|
||||
import org.springframework.data.mongodb.core.mapping.event.BeforeSaveEvent;
|
||||
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.Query;
|
||||
import org.springframework.data.mongodb.core.query.Update;
|
||||
@@ -197,6 +198,7 @@ public class MongoTemplateTests {
|
||||
template.dropCollection(TypeWithDate.class);
|
||||
template.dropCollection("collection");
|
||||
template.dropCollection("personX");
|
||||
template.dropCollection("findandreplace");
|
||||
template.dropCollection(Document.class);
|
||||
template.dropCollection(ObjectWith3AliasedFields.class);
|
||||
template.dropCollection(ObjectWith3AliasedFieldsAndNestedAddress.class);
|
||||
@@ -2374,6 +2376,54 @@ public class MongoTemplateTests {
|
||||
assertThat(retrieved.models.get(0).get(1).value(), equalTo("value2"));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1827
|
||||
public void findAndReplaceShouldReplaceDocument() {
|
||||
|
||||
org.bson.Document doc = new org.bson.Document("foo", "bar");
|
||||
template.save(doc, "findandreplace");
|
||||
|
||||
org.bson.Document replacement = new org.bson.Document("foo", "baz");
|
||||
org.bson.Document previous = template.findAndReplace(query(where("foo").is("bar")), replacement,
|
||||
FindAndReplaceOptions.options(), org.bson.Document.class, "findandreplace");
|
||||
|
||||
assertThat(previous).containsEntry("foo", "bar");
|
||||
assertThat(template.findOne(query(where("foo").is("baz")), org.bson.Document.class, "findandreplace")).isNotNull();
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1827
|
||||
public void findAndReplaceShouldReplaceObject() {
|
||||
|
||||
MyPerson person = new MyPerson("Walter");
|
||||
template.save(person);
|
||||
|
||||
MyPerson previous = template.findAndReplace(query(where("name").is("Walter")), new MyPerson("Heisenberg"));
|
||||
|
||||
assertThat(previous.getName()).isEqualTo("Walter");
|
||||
assertThat(template.findOne(query(where("name").is("Heisenberg")), MyPerson.class)).isNotNull();
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1827
|
||||
public void findAndReplaceShouldReplaceObjectReturingNew() {
|
||||
|
||||
MyPerson person = new MyPerson("Walter");
|
||||
template.save(person);
|
||||
|
||||
MyPerson updated = template.findAndReplace(query(where("name").is("Walter")), new MyPerson("Heisenberg"),
|
||||
FindAndReplaceOptions.options().returnNew(true));
|
||||
|
||||
assertThat(updated.getName()).isEqualTo("Heisenberg");
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1827
|
||||
public void findAndReplaceShouldFailWithTwoCollationObjects() {
|
||||
|
||||
thrown.expect(IllegalArgumentException.class);
|
||||
thrown.expectMessage("Both Query and FindAndReplaceOptions");
|
||||
|
||||
template.findAndReplace(query(where("name").is("Walter")).collation(Collation.of("de")), new MyPerson("Heisenberg"),
|
||||
FindAndReplaceOptions.options().collation(Collation.of("en")));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-407
|
||||
public void updatesShouldRetainTypeInformationEvenForCollections() {
|
||||
|
||||
@@ -3608,6 +3658,12 @@ public class MongoTemplateTests {
|
||||
String name;
|
||||
Address address;
|
||||
|
||||
public MyPerson() {}
|
||||
|
||||
public MyPerson(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@@ -58,12 +58,14 @@ import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.domain.Sort.Direction;
|
||||
import org.springframework.data.geo.Metrics;
|
||||
import org.springframework.data.mapping.MappingException;
|
||||
import org.springframework.data.mongodb.core.MongoTemplateTests.Address;
|
||||
import org.springframework.data.mongodb.core.MongoTemplateTests.PersonWithConvertedId;
|
||||
import org.springframework.data.mongodb.core.MongoTemplateTests.VersionedPerson;
|
||||
import org.springframework.data.mongodb.core.index.GeoSpatialIndexType;
|
||||
import org.springframework.data.mongodb.core.index.GeospatialIndex;
|
||||
import org.springframework.data.mongodb.core.index.Index;
|
||||
import org.springframework.data.mongodb.core.index.IndexOperationsAdapter;
|
||||
import org.springframework.data.mongodb.core.query.Collation;
|
||||
import org.springframework.data.mongodb.core.query.Criteria;
|
||||
import org.springframework.data.mongodb.core.query.NearQuery;
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
@@ -101,7 +103,8 @@ public class ReactiveMongoTemplateTests {
|
||||
.mergeWith(template.dropCollection(PersonWithAList.class)) //
|
||||
.mergeWith(template.dropCollection(PersonWithIdPropertyOfTypeObjectId.class)) //
|
||||
.mergeWith(template.dropCollection(PersonWithVersionPropertyOfTypeInteger.class)) //
|
||||
.mergeWith(template.dropCollection(Sample.class))) //
|
||||
.mergeWith(template.dropCollection(Sample.class)) //
|
||||
.mergeWith(template.dropCollection(MyPerson.class))) //
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@@ -453,6 +456,68 @@ public class ReactiveMongoTemplateTests {
|
||||
assertThat(p.getAge(), is(1));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1827
|
||||
public void findAndReplaceShouldReplaceDocument() {
|
||||
|
||||
org.bson.Document doc = new org.bson.Document("foo", "bar");
|
||||
template.save(doc, "findandreplace").as(StepVerifier::create).expectNextCount(1).verifyComplete();
|
||||
|
||||
org.bson.Document replacement = new org.bson.Document("foo", "baz");
|
||||
template
|
||||
.findAndReplace(query(where("foo").is("bar")), replacement, FindAndReplaceOptions.options(),
|
||||
org.bson.Document.class, "findandreplace") //
|
||||
.as(StepVerifier::create) //
|
||||
.consumeNextWith(actual -> {
|
||||
assertThat(actual, hasEntry("foo", "bar"));
|
||||
}).verifyComplete();
|
||||
|
||||
template.findOne(query(where("foo").is("baz")), org.bson.Document.class, "findandreplace") //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNextCount(1) //
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1827
|
||||
public void findAndReplaceShouldReplaceObject() {
|
||||
|
||||
MyPerson person = new MyPerson("Walter");
|
||||
template.save(person).as(StepVerifier::create).expectNextCount(1).verifyComplete();
|
||||
|
||||
template.findAndReplace(query(where("name").is("Walter")), new MyPerson("Heisenberg")) //
|
||||
.as(StepVerifier::create) //
|
||||
.consumeNextWith(actual -> {
|
||||
assertThat(actual.getName(), is("Walter"));
|
||||
}).verifyComplete();
|
||||
|
||||
template.findOne(query(where("name").is("Heisenberg")), MyPerson.class) //
|
||||
.as(StepVerifier::create).expectNextCount(1).verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1827
|
||||
public void findAndReplaceShouldReplaceObjectReturingNew() {
|
||||
|
||||
MyPerson person = new MyPerson("Walter");
|
||||
template.save(person).as(StepVerifier::create).expectNextCount(1).verifyComplete();
|
||||
|
||||
template
|
||||
.findAndReplace(query(where("name").is("Walter")), new MyPerson("Heisenberg"),
|
||||
FindAndReplaceOptions.options().returnNew(true))
|
||||
.as(StepVerifier::create) //
|
||||
.consumeNextWith(actual -> {
|
||||
assertThat(actual.getName(), is("Heisenberg"));
|
||||
}).verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1827
|
||||
public void findAndReplaceShouldFailWithTwoCollationObjects() {
|
||||
|
||||
thrown.expect(IllegalArgumentException.class);
|
||||
thrown.expectMessage("Both Query and FindAndReplaceOptions");
|
||||
|
||||
template.findAndReplace(query(where("name").is("Walter")).collation(Collation.of("de")), new MyPerson("Heisenberg"),
|
||||
FindAndReplaceOptions.options().collation(Collation.of("en")));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1444
|
||||
public void testFindAllAndRemoveFullyReturnsAndRemovesDocuments() {
|
||||
|
||||
@@ -1178,4 +1243,21 @@ public class ReactiveMongoTemplateTests {
|
||||
this.field = field;
|
||||
}
|
||||
}
|
||||
|
||||
public static class MyPerson {
|
||||
|
||||
String id;
|
||||
String name;
|
||||
Address address;
|
||||
|
||||
public MyPerson() {}
|
||||
|
||||
public MyPerson(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,6 +181,52 @@ public class ReactiveUpdateOperationSupportTests {
|
||||
}).verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1827
|
||||
public void findAndReplace() {
|
||||
|
||||
Person luke = new Person();
|
||||
luke.firstname = "Luke";
|
||||
|
||||
template.update(Person.class).matching(queryHan()).replaceWith(luke).findAndReplace() //
|
||||
.as(StepVerifier::create).expectNext(han).verifyComplete();
|
||||
|
||||
template.findOne(queryHan(), Person.class) //
|
||||
.as(StepVerifier::create) //
|
||||
.consumeNextWith(actual -> {
|
||||
assertThat(actual).isNotEqualTo(han).hasFieldOrPropertyWithValue("firstname", "Luke");
|
||||
}).verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1827
|
||||
public void findAndReplaceWithCollection() {
|
||||
|
||||
Person luke = new Person();
|
||||
luke.firstname = "Luke";
|
||||
|
||||
template.update(Person.class).inCollection(STAR_WARS).matching(queryHan()).replaceWith(luke).findAndReplace() //
|
||||
.as(StepVerifier::create).expectNext(han).verifyComplete();
|
||||
|
||||
template.findOne(queryHan(), Person.class) //
|
||||
.as(StepVerifier::create) //
|
||||
.consumeNextWith(actual -> {
|
||||
assertThat(actual).isNotEqualTo(han).hasFieldOrPropertyWithValue("firstname", "Luke");
|
||||
}).verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1827
|
||||
public void findAndReplaceWithOptions() {
|
||||
|
||||
Person luke = new Person();
|
||||
luke.firstname = "Luke";
|
||||
|
||||
template.update(Person.class).matching(queryHan()).replaceWith(luke)
|
||||
.withOptions(FindAndReplaceOptions.options().returnNew(true)).findAndReplace() //
|
||||
.as(StepVerifier::create) //
|
||||
.consumeNextWith(actual -> {
|
||||
assertThat(actual).isNotEqualTo(han).hasFieldOrPropertyWithValue("firstname", "Luke");
|
||||
}).verifyComplete();
|
||||
}
|
||||
|
||||
private Query queryHan() {
|
||||
return query(where("id").is(han.getId()));
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* <<mongo.sessions, MongoDB 3.6 Session>> support for the imperative and reactive Template APIs.
|
||||
* <<mongo.transactions, MongoDB 4.0 Transaction>> support and a MongoDB-specific transaction manager implementation.
|
||||
* <<mongodb.repositories.queries.sort,Default sort specifications for repository query methods>> using `@Query(sort=…)`.
|
||||
* `findAndReplace` support through imperative and reactive Template APIs.
|
||||
|
||||
[[new-features.2-0-0]]
|
||||
== What's New in Spring Data MongoDB 2.0
|
||||
|
||||
Reference in New Issue
Block a user