diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableUpdateOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableUpdateOperation.java
index ca6658b3c..ea7ef1966 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableUpdateOperation.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableUpdateOperation.java
@@ -19,6 +19,7 @@ import java.util.Optional;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
+import org.springframework.data.mongodb.core.query.UpdateDefinition;
import org.springframework.lang.Nullable;
import com.mongodb.client.result.UpdateResult;
@@ -151,13 +152,26 @@ public interface ExecutableUpdateOperation {
interface UpdateWithUpdate
* TypedAggregation<T> geoNear = TypedAggregation.newAggregation(entityClass, Aggregation.geoNear(near, "dis"))
* .withOptions(AggregationOptions.builder().collation(near.getCollation()).build());
@@ -678,7 +679,7 @@ public interface MongoOperations extends FluentMongoOperations {
* {@code $geoNear} aggregation command to emulate {@code geoNear} command functionality. We recommend using
* aggregations directly:
*
- *
+ *
*
* TypedAggregation<T> geoNear = TypedAggregation.newAggregation(entityClass, Aggregation.geoNear(near, "dis"))
* .withOptions(AggregationOptions.builder().collation(near.getCollation()).build());
@@ -877,6 +878,20 @@ public interface MongoOperations extends FluentMongoOperations {
return findDistinct(query, field, collection, Object.class, resultClass);
}
+ /**
+ * Triggers findAndModify
+ * to apply provided {@link Update} on documents matching {@link Criteria} of given {@link Query}.
+ *
+ * @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 update the {@link UpdateDefinition} to apply on matching documents. Must not be {@literal null}.
+ * @param entityClass the parametrized type. Must not be {@literal null}.
+ * @return the converted object that was updated before it was updated or {@literal null}, if not found.
+ * @since 2.3
+ */
+ @Nullable
+ T findAndModify(Query query, UpdateDefinition update, Class entityClass);
+
/**
* Triggers findAndModify
* to apply provided {@link Update} on documents matching {@link Criteria} of given {@link Query}.
@@ -886,9 +901,28 @@ public interface MongoOperations extends FluentMongoOperations {
* @param update the {@link Update} to apply on matching documents. Must not be {@literal null}.
* @param entityClass the parametrized type. Must not be {@literal null}.
* @return the converted object that was updated before it was updated or {@literal null}, if not found.
+ * @deprecated since 2.3 in favor of {@link #findAndModify(Query, UpdateDefinition, Class)}.
+ */
+ @Deprecated
+ @Nullable
+ default T findAndModify(Query query, Update update, Class entityClass) {
+ return findAndModify(query, (UpdateDefinition) update, entityClass);
+ }
+
+ /**
+ * Triggers findAndModify
+ * to apply provided {@link Update} on documents matching {@link Criteria} of given {@link Query}.
+ *
+ * @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 update the {@link UpdateDefinition} to apply on matching documents. 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 before it was updated or {@literal null}, if not found.
+ * @since 2.3
*/
@Nullable
- T findAndModify(Query query, Update update, Class entityClass);
+ T findAndModify(Query query, UpdateDefinition update, Class entityClass, String collectionName);
/**
* Triggers findAndModify
@@ -900,9 +934,31 @@ public interface MongoOperations extends FluentMongoOperations {
* @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 before it was updated or {@literal null}, if not found.
+ * @deprecated since 2.3 in favor of {@link #findAndModify(Query, UpdateDefinition, Class, String)}.
+ */
+ @Deprecated
+ @Nullable
+ default T findAndModify(Query query, Update update, Class entityClass, String collectionName) {
+ return findAndModify(query, (UpdateDefinition) update, entityClass, collectionName);
+ }
+
+ /**
+ * Triggers findAndModify
+ * to apply provided {@link Update} on documents matching {@link Criteria} of given {@link Query} taking
+ * {@link FindAndModifyOptions} into account.
+ *
+ * @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
+ * fields specification.
+ * @param update the {@link UpdateDefinition} to apply on matching documents.
+ * @param options the {@link FindAndModifyOptions} holding additional information.
+ * @param entityClass the parametrized type.
+ * @return the converted object that was updated or {@literal null}, if not found. Depending on the value of
+ * {@link FindAndModifyOptions#isReturnNew()} this will either be the object as it was before the update or as
+ * it is after the update.
+ * @since 2.3
*/
@Nullable
- T findAndModify(Query query, Update update, Class entityClass, String collectionName);
+ T findAndModify(Query query, UpdateDefinition update, FindAndModifyOptions options, Class entityClass);
/**
* Triggers findAndModify
@@ -917,9 +973,33 @@ public interface MongoOperations extends FluentMongoOperations {
* @return the converted object that was updated or {@literal null}, if not found. Depending on the value of
* {@link FindAndModifyOptions#isReturnNew()} this will either be the object as it was before the update or as
* it is after the update.
+ * @deprecated since 2.3 in favor of {@link #findAndModify(Query, UpdateDefinition, FindAndModifyOptions, Class)}
*/
@Nullable
- T findAndModify(Query query, Update update, FindAndModifyOptions options, Class entityClass);
+ @Deprecated
+ default T findAndModify(Query query, Update update, FindAndModifyOptions options, Class entityClass) {
+ return findAndModify(query, (UpdateDefinition) update, options, entityClass);
+ }
+
+ /**
+ * Triggers findAndModify
+ * to apply provided {@link Update} on documents matching {@link Criteria} of given {@link Query} taking
+ * {@link FindAndModifyOptions} 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 update the {@link UpdateDefinition} to apply on matching documents. 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 FindAndModifyOptions#isReturnNew()} this will either be the object as it was before the update or as
+ * it is after the update.
+ * @since 2.3
+ */
+ @Nullable
+ T findAndModify(Query query, UpdateDefinition update, FindAndModifyOptions options, Class entityClass,
+ String collectionName);
/**
* Triggers findAndModify
@@ -935,10 +1015,15 @@ public interface MongoOperations extends FluentMongoOperations {
* @return the converted object that was updated or {@literal null}, if not found. Depending on the value of
* {@link FindAndModifyOptions#isReturnNew()} this will either be the object as it was before the update or as
* it is after the update.
+ * @deprecated since 2.3 in favor of
+ * {@link #findAndModify(Query, UpdateDefinition, FindAndModifyOptions, Class, String)}.
*/
+ @Deprecated
@Nullable
- T findAndModify(Query query, Update update, FindAndModifyOptions options, Class entityClass,
- String collectionName);
+ default T findAndModify(Query query, Update update, FindAndModifyOptions options, Class entityClass,
+ String collectionName) {
+ return findAndModify(query, (UpdateDefinition) update, options, entityClass, collectionName);
+ }
/**
* Triggers
@@ -1294,8 +1379,26 @@ public interface MongoOperations extends FluentMongoOperations {
* object. Must not be {@literal null}.
* @param entityClass class that determines the collection to use. Must not be {@literal null}.
* @return the {@link UpdateResult} which lets you access the results of the previous write.
+ * @since 2.3
*/
- UpdateResult upsert(Query query, Update update, Class> entityClass);
+ UpdateResult upsert(Query query, UpdateDefinition update, Class> entityClass);
+
+ /**
+ * Performs an upsert. If no document is found that matches the query, a new document is created and inserted by
+ * combining the query document and the update document.
+ *
+ * @param query the query document that specifies the criteria used to select a record to be upserted. Must not be
+ * {@literal null}.
+ * @param update the update document that contains the updated object or $ operators to manipulate the existing
+ * object. Must not be {@literal null}.
+ * @param entityClass class that determines the collection to use. Must not be {@literal null}.
+ * @return the {@link UpdateResult} which lets you access the results of the previous write.
+ * @deprecated since 2.3 in favor of {@link #upsert(Query, UpdateDefinition, Class)}
+ */
+ @Deprecated
+ default UpdateResult upsert(Query query, Update update, Class> entityClass) {
+ return upsert(query, (UpdateDefinition) update, entityClass);
+ }
/**
* Performs an upsert. If no document is found that matches the query, a new document is created and inserted by
@@ -1312,8 +1415,43 @@ public interface MongoOperations extends FluentMongoOperations {
* object. Must not be {@literal null}.
* @param collectionName name of the collection to update the object in.
* @return the {@link UpdateResult} which lets you access the results of the previous write.
+ * @since 2.3
*/
- UpdateResult upsert(Query query, Update update, String collectionName);
+ UpdateResult upsert(Query query, UpdateDefinition update, String collectionName);
+
+ /**
+ * Performs an upsert. If no document is found that matches the query, a new document is created and inserted by
+ * combining the query document and the update document.
+ * NOTE: Any additional support for field mapping, versions, etc. is not available due to the lack of
+ * domain type information. Use {@link #upsert(Query, Update, Class, String)} to get full type specific support.
+ *
+ * @param query the query document that specifies the criteria used to select a record to be upserted. Must not be
+ * {@literal null}.
+ * @param update the update document that contains the updated object or $ operators to manipulate the existing
+ * object. Must not be {@literal null}.
+ * @param collectionName name of the collection to update the object in.
+ * @return the {@link UpdateResult} which lets you access the results of the previous write.
+ * @deprecated since 2.3 in favor of {@link #upsert(Query, UpdateDefinition, String)}
+ */
+ @Deprecated
+ default UpdateResult upsert(Query query, Update update, String collectionName) {
+ return upsert(query, (UpdateDefinition) update, collectionName);
+ }
+
+ /**
+ * Performs an upsert. If no document is found that matches the query, a new document is created and inserted by
+ * combining the query document and the update document.
+ *
+ * @param query the query document that specifies the criteria used to select a record to be upserted. Must not be
+ * {@literal null}.
+ * @param update the update document that contains the updated object or $ operators to manipulate the existing
+ * object. Must not be {@literal null}.
+ * @param entityClass class of the pojo to be operated on. Must not be {@literal null}.
+ * @param collectionName name of the collection to update the object in. Must not be {@literal null}.
+ * @return the {@link UpdateResult} which lets you access the results of the previous write.
+ * @since 2.3
+ */
+ UpdateResult upsert(Query query, UpdateDefinition update, Class> entityClass, String collectionName);
/**
* Performs an upsert. If no document is found that matches the query, a new document is created and inserted by
@@ -1328,8 +1466,26 @@ public interface MongoOperations extends FluentMongoOperations {
* @param entityClass class of the pojo to be operated on. Must not be {@literal null}.
* @param collectionName name of the collection to update the object in. Must not be {@literal null}.
* @return the {@link UpdateResult} which lets you access the results of the previous write.
+ * @deprecated since 2.3 in favor of {@link #upsert(Query, UpdateDefinition, Class, String)}
*/
- UpdateResult upsert(Query query, Update update, Class> entityClass, String collectionName);
+ @Deprecated
+ default UpdateResult upsert(Query query, Update update, Class> entityClass, String collectionName) {
+ return upsert(query, (UpdateDefinition) update, entityClass, collectionName);
+ }
+
+ /**
+ * Updates the first object that is found in the collection of the entity class that matches the query document with
+ * the provided update document.
+ *
+ * @param query the query document that specifies the criteria used to select a record to be updated. Must not be
+ * {@literal null}.
+ * @param update the update document that contains the updated object or $ operators to manipulate the existing. Must
+ * not be {@literal null}.
+ * @param entityClass class that determines the collection to use.
+ * @return the {@link UpdateResult} which lets you access the results of the previous write.
+ * @since 2.3
+ */
+ UpdateResult updateFirst(Query query, UpdateDefinition update, Class> entityClass);
/**
* Updates the first object that is found in the collection of the entity class that matches the query document with
@@ -1343,8 +1499,12 @@ public interface MongoOperations extends FluentMongoOperations {
* not be {@literal null}.
* @param entityClass class that determines the collection to use.
* @return the {@link UpdateResult} which lets you access the results of the previous write.
+ * @deprecated since 2.3 in favor of {@link #updateFirst(Query, UpdateDefinition, Class)}.
*/
- UpdateResult updateFirst(Query query, Update update, Class> entityClass);
+ @Deprecated
+ default UpdateResult updateFirst(Query query, Update update, Class> entityClass) {
+ return updateFirst(query, (UpdateDefinition) update, entityClass);
+ }
/**
* Updates the first object that is found in the specified collection that matches the query document criteria with
@@ -1361,8 +1521,43 @@ public interface MongoOperations extends FluentMongoOperations {
* not be {@literal null}.
* @param collectionName name of the collection to update the object in. Must not be {@literal null}.
* @return the {@link UpdateResult} which lets you access the results of the previous write.
+ * @since 2.3
*/
- UpdateResult updateFirst(Query query, Update update, String collectionName);
+ UpdateResult updateFirst(Query query, UpdateDefinition update, String collectionName);
+
+ /**
+ * Updates the first object that is found in the specified collection that matches the query document criteria with
+ * the provided updated document.
+ * NOTE: Any additional support for field mapping, versions, etc. is not available due to the lack of
+ * domain type information. Use {@link #updateFirst(Query, Update, Class, String)} to get full type specific support.
+ *
+ * @param query the query document that specifies the criteria used to select a record to be updated. Must not be
+ * {@literal null}.
+ * @param update the update document that contains the updated object or $ operators to manipulate the existing. Must
+ * not be {@literal null}.
+ * @param collectionName name of the collection to update the object in. Must not be {@literal null}.
+ * @return the {@link UpdateResult} which lets you access the results of the previous write.
+ * @deprecated since 2.3 in favor of {@link #updateFirst(Query, UpdateDefinition, String)}.
+ */
+ @Deprecated
+ default UpdateResult updateFirst(Query query, Update update, String collectionName) {
+ return updateFirst(query, (UpdateDefinition) update, collectionName);
+ }
+
+ /**
+ * Updates the first object that is found in the specified collection that matches the query document criteria with
+ * the provided updated document.
+ *
+ * @param query the query document that specifies the criteria used to select a record to be updated. Must not be
+ * {@literal null}.
+ * @param update the update document that contains the updated object or $ operators to manipulate the existing. Must
+ * not be {@literal null}.
+ * @param entityClass class of the pojo to be operated on. Must not be {@literal null}.
+ * @param collectionName name of the collection to update the object in. Must not be {@literal null}.
+ * @return the {@link UpdateResult} which lets you access the results of the previous write.
+ * @since 2.3
+ */
+ UpdateResult updateFirst(Query query, UpdateDefinition update, Class> entityClass, String collectionName);
/**
* Updates the first object that is found in the specified collection that matches the query document criteria with
@@ -1377,8 +1572,12 @@ public interface MongoOperations extends FluentMongoOperations {
* @param entityClass class of the pojo to be operated on. Must not be {@literal null}.
* @param collectionName name of the collection to update the object in. Must not be {@literal null}.
* @return the {@link UpdateResult} which lets you access the results of the previous write.
+ * @deprecated since 2.3 in favor of {@link #updateFirst(Query, UpdateDefinition, Class, String)}.
*/
- UpdateResult updateFirst(Query query, Update update, Class> entityClass, String collectionName);
+ @Deprecated
+ default UpdateResult updateFirst(Query query, Update update, Class> entityClass, String collectionName) {
+ return updateFirst(query, (UpdateDefinition) update, entityClass, collectionName);
+ }
/**
* Updates all objects that are found in the collection for the entity class that matches the query document criteria
@@ -1390,8 +1589,26 @@ public interface MongoOperations extends FluentMongoOperations {
* not be {@literal null}.
* @param entityClass class of the pojo to be operated on. Must not be {@literal null}.
* @return the {@link UpdateResult} which lets you access the results of the previous write.
+ * @since 2.3
*/
- UpdateResult updateMulti(Query query, Update update, Class> entityClass);
+ UpdateResult updateMulti(Query query, UpdateDefinition update, Class> entityClass);
+
+ /**
+ * Updates all objects that are found in the collection for the entity class that matches the query document criteria
+ * with the provided updated document.
+ *
+ * @param query the query document that specifies the criteria used to select a record to be updated. Must not be
+ * {@literal null}.
+ * @param update the update document that contains the updated object or $ operators to manipulate the existing. Must
+ * not be {@literal null}.
+ * @param entityClass class of the pojo to be operated on. Must not be {@literal null}.
+ * @return the {@link UpdateResult} which lets you access the results of the previous write.
+ * @deprecated since 2.3 in favor of {@link #updateMulti(Query, UpdateDefinition, Class)}.
+ */
+ @Deprecated
+ default UpdateResult updateMulti(Query query, Update update, Class> entityClass) {
+ return updateMulti(query, (UpdateDefinition) update, entityClass);
+ }
/**
* Updates all objects that are found in the specified collection that matches the query document criteria with the
@@ -1405,8 +1622,28 @@ public interface MongoOperations extends FluentMongoOperations {
* not be {@literal null}.
* @param collectionName name of the collection to update the object in. Must not be {@literal null}.
* @return the {@link UpdateResult} which lets you access the results of the previous write.
+ * @since 2.3
*/
- UpdateResult updateMulti(Query query, Update update, String collectionName);
+ UpdateResult updateMulti(Query query, UpdateDefinition update, String collectionName);
+
+ /**
+ * Updates all objects that are found in the specified collection that matches the query document criteria with the
+ * provided updated document.
+ * NOTE: Any additional support for field mapping, versions, etc. is not available due to the lack of
+ * domain type information. Use {@link #updateMulti(Query, Update, Class, String)} to get full type specific support.
+ *
+ * @param query the query document that specifies the criteria used to select a record to be updated. Must not be
+ * {@literal null}.
+ * @param update the update document that contains the updated object or $ operators to manipulate the existing. Must
+ * not be {@literal null}.
+ * @param collectionName name of the collection to update the object in. Must not be {@literal null}.
+ * @return the {@link UpdateResult} which lets you access the results of the previous write.
+ * @deprecated since 2.3 in favor of {@link #updateMulti(Query, UpdateDefinition, String)}.
+ */
+ @Deprecated
+ default UpdateResult updateMulti(Query query, Update update, String collectionName) {
+ return updateMulti(query, (UpdateDefinition) update, collectionName);
+ }
/**
* Updates all objects that are found in the collection for the entity class that matches the query document criteria
@@ -1419,8 +1656,27 @@ public interface MongoOperations extends FluentMongoOperations {
* @param entityClass class of the pojo to be operated on. Must not be {@literal null}.
* @param collectionName name of the collection to update the object in. Must not be {@literal null}.
* @return the {@link UpdateResult} which lets you access the results of the previous write.
+ * @since 2.3
*/
- UpdateResult updateMulti(Query query, Update update, Class> entityClass, String collectionName);
+ UpdateResult updateMulti(Query query, UpdateDefinition update, Class> entityClass, String collectionName);
+
+ /**
+ * Updates all objects that are found in the collection for the entity class that matches the query document criteria
+ * with the provided updated document.
+ *
+ * @param query the query document that specifies the criteria used to select a record to be updated. Must not be
+ * {@literal null}.
+ * @param update the update document that contains the updated object or $ operators to manipulate the existing. Must
+ * not be {@literal null}.
+ * @param entityClass class of the pojo to be operated on. Must not be {@literal null}.
+ * @param collectionName name of the collection to update the object in. Must not be {@literal null}.
+ * @return the {@link UpdateResult} which lets you access the results of the previous write.
+ * @deprecated since 2.3 in favor of {@link #updateMulti(Query, UpdateDefinition, Class, String)}.
+ */
+ @Deprecated
+ default UpdateResult updateMulti(Query query, Update update, Class> entityClass, String collectionName) {
+ return updateMulti(query, (UpdateDefinition) update, entityClass, collectionName);
+ }
/**
* Remove the given object from the collection by {@literal id} and (if applicable) its
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java
index 9c3b0d6b5..a2f2a804e 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java
@@ -69,7 +69,9 @@ import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.AggregationOperationContext;
import org.springframework.data.mongodb.core.aggregation.AggregationOptions;
import org.springframework.data.mongodb.core.aggregation.AggregationResults;
+import org.springframework.data.mongodb.core.aggregation.AggregationUpdate;
import org.springframework.data.mongodb.core.aggregation.Fields;
+import org.springframework.data.mongodb.core.aggregation.RelaxedTypeBasedAggregationOperationContext;
import org.springframework.data.mongodb.core.aggregation.TypeBasedAggregationOperationContext;
import org.springframework.data.mongodb.core.aggregation.TypedAggregation;
import org.springframework.data.mongodb.core.convert.DbRefResolver;
@@ -109,7 +111,6 @@ import org.springframework.data.mongodb.core.query.Meta;
import org.springframework.data.mongodb.core.query.Meta.CursorOption;
import org.springframework.data.mongodb.core.query.NearQuery;
import org.springframework.data.mongodb.core.query.Query;
-import org.springframework.data.mongodb.core.query.Update;
import org.springframework.data.mongodb.core.query.UpdateDefinition;
import org.springframework.data.mongodb.core.query.UpdateDefinition.ArrayFilter;
import org.springframework.data.mongodb.core.validation.Validator;
@@ -1046,25 +1047,25 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
@Nullable
@Override
- public T findAndModify(Query query, Update update, Class entityClass) {
+ public T findAndModify(Query query, UpdateDefinition update, Class entityClass) {
return findAndModify(query, update, new FindAndModifyOptions(), entityClass, getCollectionName(entityClass));
}
@Nullable
@Override
- public T findAndModify(Query query, Update update, Class entityClass, String collectionName) {
+ public T findAndModify(Query query, UpdateDefinition update, Class entityClass, String collectionName) {
return findAndModify(query, update, new FindAndModifyOptions(), entityClass, collectionName);
}
@Nullable
@Override
- public T findAndModify(Query query, Update update, FindAndModifyOptions options, Class entityClass) {
+ public T findAndModify(Query query, UpdateDefinition update, FindAndModifyOptions options, Class entityClass) {
return findAndModify(query, update, options, entityClass, getCollectionName(entityClass));
}
@Nullable
@Override
- public T findAndModify(Query query, Update update, FindAndModifyOptions options, Class entityClass,
+ public T findAndModify(Query query, UpdateDefinition update, FindAndModifyOptions options, Class entityClass,
String collectionName) {
Assert.notNull(query, "Query must not be null!");
@@ -1564,17 +1565,17 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
}
@Override
- public UpdateResult upsert(Query query, Update update, Class> entityClass) {
+ public UpdateResult upsert(Query query, UpdateDefinition update, Class> entityClass) {
return doUpdate(getCollectionName(entityClass), query, update, entityClass, true, false);
}
@Override
- public UpdateResult upsert(Query query, Update update, String collectionName) {
+ public UpdateResult upsert(Query query, UpdateDefinition update, String collectionName) {
return doUpdate(collectionName, query, update, null, true, false);
}
@Override
- public UpdateResult upsert(Query query, Update update, Class> entityClass, String collectionName) {
+ public UpdateResult upsert(Query query, UpdateDefinition update, Class> entityClass, String collectionName) {
Assert.notNull(entityClass, "EntityClass must not be null!");
@@ -1582,17 +1583,17 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
}
@Override
- public UpdateResult updateFirst(Query query, Update update, Class> entityClass) {
+ public UpdateResult updateFirst(Query query, UpdateDefinition update, Class> entityClass) {
return doUpdate(getCollectionName(entityClass), query, update, entityClass, false, false);
}
@Override
- public UpdateResult updateFirst(final Query query, final Update update, final String collectionName) {
+ public UpdateResult updateFirst(final Query query, final UpdateDefinition update, final String collectionName) {
return doUpdate(collectionName, query, update, null, false, false);
}
@Override
- public UpdateResult updateFirst(Query query, Update update, Class> entityClass, String collectionName) {
+ public UpdateResult updateFirst(Query query, UpdateDefinition update, Class> entityClass, String collectionName) {
Assert.notNull(entityClass, "EntityClass must not be null!");
@@ -1600,17 +1601,18 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
}
@Override
- public UpdateResult updateMulti(Query query, Update update, Class> entityClass) {
+ public UpdateResult updateMulti(Query query, UpdateDefinition update, Class> entityClass) {
return doUpdate(getCollectionName(entityClass), query, update, entityClass, false, true);
}
@Override
- public UpdateResult updateMulti(final Query query, final Update update, String collectionName) {
+ public UpdateResult updateMulti(final Query query, final UpdateDefinition update, String collectionName) {
return doUpdate(collectionName, query, update, null, false, true);
}
@Override
- public UpdateResult updateMulti(final Query query, final Update update, Class> entityClass, String collectionName) {
+ public UpdateResult updateMulti(final Query query, final UpdateDefinition update, Class> entityClass,
+ String collectionName) {
Assert.notNull(entityClass, "EntityClass must not be null!");
@@ -1631,25 +1633,53 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
upsert ? "Upsert" : "UpdateFirst", serializeToJsonSafely(query.getSortObject()));
}
+ MongoPersistentEntity> entity = entityClass == null ? null : getPersistentEntity(entityClass);
+ increaseVersionForUpdateIfNecessary(entity, update);
+
+ UpdateOptions opts = new UpdateOptions();
+ opts.upsert(upsert);
+
+ if (update.hasArrayFilters()) {
+ opts.arrayFilters(update.getArrayFilters().stream().map(ArrayFilter::asDocument).collect(Collectors.toList()));
+ }
+
+ Document queryObj = new Document();
+
+ if (query != null) {
+ queryObj.putAll(queryMapper.getMappedObject(query.getQueryObject(), entity));
+ }
+
+ if (multi && update.isIsolated() && !queryObj.containsKey("$isolated")) {
+ queryObj.put("$isolated", 1);
+ }
+
+ if (update instanceof AggregationUpdate) {
+
+ AggregationOperationContext context = entityClass != null
+ ? new RelaxedTypeBasedAggregationOperationContext(entityClass, mappingContext, queryMapper)
+ : Aggregation.DEFAULT_CONTEXT;
+
+ AggregationUpdate aUppdate = ((AggregationUpdate) update);
+ List pipeline = new AggregationUtil(queryMapper, mappingContext).createPipeline(aUppdate, context);
+
+ return execute(collectionName, collection -> {
+
+ MongoAction mongoAction = new MongoAction(writeConcern, MongoActionOperation.UPDATE, collectionName,
+ entityClass, update.getUpdateObject(), queryObj);
+ WriteConcern writeConcernToUse = prepareWriteConcern(mongoAction);
+
+ collection = writeConcernToUse != null ? collection.withWriteConcern(writeConcernToUse) : collection;
+
+ if (multi) {
+ return collection.updateMany(queryObj, pipeline, opts);
+ }
+
+ return collection.updateOne(queryObj, pipeline, opts);
+ });
+ }
+
return execute(collectionName, collection -> {
- MongoPersistentEntity> entity = entityClass == null ? null : getPersistentEntity(entityClass);
-
- increaseVersionForUpdateIfNecessary(entity, update);
-
- UpdateOptions opts = new UpdateOptions();
- opts.upsert(upsert);
-
- if (update.hasArrayFilters()) {
- opts.arrayFilters(update.getArrayFilters().stream().map(ArrayFilter::asDocument).collect(Collectors.toList()));
- }
-
- Document queryObj = new Document();
-
- if (query != null) {
- queryObj.putAll(queryMapper.getMappedObject(query.getQueryObject(), entity));
- }
-
operations.forType(entityClass) //
.getCollation(query) //
.map(Collation::toMongoCollation) //
@@ -1658,10 +1688,6 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
Document updateObj = update instanceof MappedUpdate ? update.getUpdateObject()
: updateMapper.getMappedObject(update.getUpdateObject(), entity);
- if (multi && update.isIsolated() && !queryObj.containsKey("$isolated")) {
- queryObj.put("$isolated", 1);
- }
-
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Calling update using query: {} and update: {} in collection: {}", serializeToJsonSafely(queryObj),
serializeToJsonSafely(updateObj), collectionName);
@@ -2653,7 +2679,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
@SuppressWarnings("ConstantConditions")
protected T doFindAndModify(String collectionName, Document query, Document fields, Document sort,
- Class entityClass, Update update, @Nullable FindAndModifyOptions options) {
+ Class entityClass, UpdateDefinition update, @Nullable FindAndModifyOptions options) {
EntityReader super T, Bson> readerToUse = this.mongoConverter;
@@ -2666,7 +2692,18 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
increaseVersionForUpdateIfNecessary(entity, update);
Document mappedQuery = queryMapper.getMappedObject(query, entity);
- Document mappedUpdate = updateMapper.getMappedObject(update.getUpdateObject(), entity);
+
+ Object mappedUpdate = new Document();
+ if (update instanceof AggregationUpdate) {
+
+ AggregationOperationContext context = entityClass != null
+ ? new RelaxedTypeBasedAggregationOperationContext(entityClass, mappingContext, queryMapper)
+ : Aggregation.DEFAULT_CONTEXT;
+
+ mappedUpdate = new AggregationUtil(queryMapper, mappingContext).createPipeline((Aggregation) update, context);
+ } else {
+ mappedUpdate = updateMapper.getMappedObject(update.getUpdateObject(), entity);
+ }
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(
@@ -3040,11 +3077,11 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
private final Document query;
private final Document fields;
private final Document sort;
- private final Document update;
+ private final Object update;
private final List arrayFilters;
private final FindAndModifyOptions options;
- public FindAndModifyCallback(Document query, Document fields, Document sort, Document update,
+ public FindAndModifyCallback(Document query, Document fields, Document sort, Object update,
List arrayFilters, FindAndModifyOptions options) {
this.query = query;
this.fields = fields;
@@ -3072,7 +3109,12 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
opts.arrayFilters(arrayFilters);
}
- return collection.findOneAndUpdate(query, update, opts);
+ if (update instanceof Document) {
+ return collection.findOneAndUpdate(query, (Document) update, opts);
+ } else if (update instanceof List) {
+ return collection.findOneAndUpdate(query, (List) update, opts);
+ }
+ throw new IllegalArgumentException("doh - that does not work");
}
}
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoOperations.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoOperations.java
index 9d68fca4a..93bde21ee 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoOperations.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoOperations.java
@@ -39,6 +39,7 @@ import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.NearQuery;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
+import org.springframework.data.mongodb.core.query.UpdateDefinition;
import org.springframework.lang.Nullable;
import org.springframework.transaction.reactive.TransactionalOperator;
import org.springframework.util.Assert;
@@ -672,6 +673,19 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
@Deprecated
Flux> geoNear(NearQuery near, Class entityClass, String collectionName);
+ /**
+ * Triggers findAndModify
+ * to apply provided {@link Update} on documents matching {@link Criteria} of given {@link Query}.
+ *
+ * @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 update the {@link UpdateDefinition} to apply on matching documents. Must not be {@literal null}.
+ * @param entityClass the parametrized type. Must not be {@literal null}.
+ * @return the converted object that was updated before it was updated.
+ * @since 2.3
+ */
+ Mono findAndModify(Query query, UpdateDefinition update, Class entityClass);
+
/**
* Triggers findAndModify
* to apply provided {@link Update} on documents matching {@link Criteria} of given {@link Query}.
@@ -681,8 +695,26 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
* @param update the {@link Update} to apply on matching documents. Must not be {@literal null}.
* @param entityClass the parametrized type. Must not be {@literal null}.
* @return the converted object that was updated before it was updated.
+ * @deprecated since 2.3 in favor of {@link #findAndModify(Query, UpdateDefinition, Class)}.
*/
- Mono findAndModify(Query query, Update update, Class entityClass);
+ @Deprecated
+ default Mono findAndModify(Query query, Update update, Class entityClass) {
+ return findAndModify(query, (UpdateDefinition) update, entityClass);
+ }
+
+ /**
+ * Triggers findAndModify
+ * to apply provided {@link Update} on documents matching {@link Criteria} of given {@link Query}.
+ *
+ * @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 update the {@link UpdateDefinition} to apply on matching documents. 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 before it was updated.
+ * @since 2.3
+ */
+ Mono findAndModify(Query query, UpdateDefinition update, Class entityClass, String collectionName);
/**
* Triggers findAndModify
@@ -694,8 +726,28 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
* @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 before it was updated.
+ * @deprecated since 2.3 in favor of {@link #findAndModify(Query, UpdateDefinition, Class, String)}.
*/
- Mono findAndModify(Query query, Update update, Class entityClass, String collectionName);
+ @Deprecated
+ default Mono findAndModify(Query query, Update update, Class entityClass, String collectionName) {
+ return findAndModify(query, (UpdateDefinition) update, entityClass, collectionName);
+ }
+
+ /**
+ * Triggers findAndModify
+ * to apply provided {@link Update} on documents matching {@link Criteria} of given {@link Query} taking
+ * {@link FindAndModifyOptions} into account.
+ *
+ * @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
+ * fields specification.
+ * @param update the {@link UpdateDefinition} to apply on matching documents.
+ * @param options the {@link FindAndModifyOptions} holding additional information.
+ * @param entityClass the parametrized type.
+ * @return the converted object that was updated. Depending on the value of {@link FindAndModifyOptions#isReturnNew()}
+ * this will either be the object as it was before the update or as it is after the update.
+ * @since 2.3
+ */
+ Mono findAndModify(Query query, UpdateDefinition update, FindAndModifyOptions options, Class entityClass);
/**
* Triggers findAndModify
@@ -709,8 +761,30 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
* @param entityClass the parametrized type.
* @return the converted object that was updated. Depending on the value of {@link FindAndModifyOptions#isReturnNew()}
* this will either be the object as it was before the update or as it is after the update.
+ * @deprecated since 2.3 in favor of {@link #findAndModify(Query, UpdateDefinition, FindAndModifyOptions, Class)}.
*/
- Mono findAndModify(Query query, Update update, FindAndModifyOptions options, Class entityClass);
+ @Deprecated
+ default Mono findAndModify(Query query, Update update, FindAndModifyOptions options, Class entityClass) {
+ return findAndModify(query, (UpdateDefinition) update, options, entityClass);
+ }
+
+ /**
+ * Triggers findAndModify
+ * to apply provided {@link Update} on documents matching {@link Criteria} of given {@link Query} taking
+ * {@link FindAndModifyOptions} 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 update the {@link UpdateDefinition} to apply on matching documents. 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. Depending on the value of {@link FindAndModifyOptions#isReturnNew()}
+ * this will either be the object as it was before the update or as it is after the update.
+ * @since 2.3
+ */
+ Mono findAndModify(Query query, UpdateDefinition update, FindAndModifyOptions options, Class entityClass,
+ String collectionName);
/**
* Triggers findAndModify
@@ -725,9 +799,14 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
* @param collectionName the collection to query. Must not be {@literal null}.
* @return the converted object that was updated. Depending on the value of {@link FindAndModifyOptions#isReturnNew()}
* this will either be the object as it was before the update or as it is after the update.
+ * @deprecated since 2.3 in favor of
+ * {@link #findAndModify(Query, UpdateDefinition, FindAndModifyOptions, Class, String)}.
*/
- Mono findAndModify(Query query, Update update, FindAndModifyOptions options, Class entityClass,
- String collectionName);
+ @Deprecated
+ default Mono findAndModify(Query query, Update update, FindAndModifyOptions options, Class entityClass,
+ String collectionName) {
+ return findAndModify(query, (UpdateDefinition) update, options, entityClass, collectionName);
+ }
/**
* Triggers
@@ -1157,8 +1236,42 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
* object. Must not be {@literal null}.
* @param entityClass class that determines the collection to use. Must not be {@literal null}.
* @return the {@link UpdateResult} which lets you access the results of the previous write.
+ * @since 2.3
*/
- Mono upsert(Query query, Update update, Class> entityClass);
+ Mono upsert(Query query, UpdateDefinition update, Class> entityClass);
+
+ /**
+ * Performs an upsert. If no document is found that matches the query, a new document is created and inserted by
+ * combining the query document and the update document.
+ *
+ * @param query the query document that specifies the criteria used to select a record to be upserted. Must not be
+ * {@literal null}.
+ * @param update the update document that contains the updated object or $ operators to manipulate the existing
+ * object. Must not be {@literal null}.
+ * @param entityClass class that determines the collection to use. Must not be {@literal null}.
+ * @return the {@link UpdateResult} which lets you access the results of the previous write.
+ * @deprecated since 2.3 in favor of {@link #upsert(Query, UpdateDefinition, Class)}.
+ */
+ @Deprecated
+ default Mono upsert(Query query, Update update, Class> entityClass) {
+ return upsert(query, (UpdateDefinition) update, entityClass);
+ }
+
+ /**
+ * Performs an upsert. If no document is found that matches the query, a new document is created and inserted by
+ * combining the query document and the update document.
+ * NOTE: Any additional support for field mapping, versions, etc. is not available due to the lack of
+ * domain type information. Use {@link #upsert(Query, Update, Class, String)} to get full type specific support.
+ *
+ * @param query the query document that specifies the criteria used to select a record to be upserted. Must not be
+ * {@literal null}.
+ * @param update the update document that contains the updated object or $ operators to manipulate the existing
+ * object. Must not be {@literal null}.
+ * @param collectionName name of the collection to update the object in.
+ * @return the {@link UpdateResult} which lets you access the results of the previous write.
+ * @since 2.3
+ */
+ Mono upsert(Query query, UpdateDefinition update, String collectionName);
/**
* Performs an upsert. If no document is found that matches the query, a new document is created and inserted by
@@ -1175,8 +1288,27 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
* object. Must not be {@literal null}.
* @param collectionName name of the collection to update the object in.
* @return the {@link UpdateResult} which lets you access the results of the previous write.
+ * @deprecated since 2.3 in favor of {@link #upsert(Query, UpdateDefinition, String)}.
*/
- Mono upsert(Query query, Update update, String collectionName);
+ @Deprecated
+ default Mono upsert(Query query, Update update, String collectionName) {
+ return upsert(query, (UpdateDefinition) update, collectionName);
+ }
+
+ /**
+ * Performs an upsert. If no document is found that matches the query, a new document is created and inserted by
+ * combining the query document and the update document.
+ *
+ * @param query the query document that specifies the criteria used to select a record to be upserted. Must not be
+ * {@literal null}.
+ * @param update the update document that contains the updated object or $ operators to manipulate the existing
+ * object. Must not be {@literal null}.
+ * @param entityClass class of the pojo to be operated on. Must not be {@literal null}.
+ * @param collectionName name of the collection to update the object in. Must not be {@literal null}.
+ * @return the {@link UpdateResult} which lets you access the results of the previous write.
+ * @since 2.3
+ */
+ Mono upsert(Query query, UpdateDefinition update, Class> entityClass, String collectionName);
/**
* Performs an upsert. If no document is found that matches the query, a new document is created and inserted by
@@ -1191,8 +1323,12 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
* @param entityClass class of the pojo to be operated on. Must not be {@literal null}.
* @param collectionName name of the collection to update the object in. Must not be {@literal null}.
* @return the {@link UpdateResult} which lets you access the results of the previous write.
+ * @deprecated since 2.3 in favor of {@link #upsert(Query, UpdateDefinition, Class, String)}.
*/
- Mono upsert(Query query, Update update, Class> entityClass, String collectionName);
+ @Deprecated
+ default Mono upsert(Query query, Update update, Class> entityClass, String collectionName) {
+ return upsert(query, (UpdateDefinition) update, entityClass, collectionName);
+ }
/**
* Updates the first object that is found in the collection of the entity class that matches the query document with
@@ -1206,8 +1342,26 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
* not be {@literal null}.
* @param entityClass class that determines the collection to use.
* @return the {@link UpdateResult} which lets you access the results of the previous write.
+ * @since 2.3
*/
- Mono updateFirst(Query query, Update update, Class> entityClass);
+ Mono updateFirst(Query query, UpdateDefinition update, Class> entityClass);
+
+ /**
+ * Updates the first object that is found in the collection of the entity class that matches the query document with
+ * the provided update document.
+ *
+ * @param query the query document that specifies the criteria used to select a record to be updated. Must not be
+ * {@literal null}.
+ * @param update the update document that contains the updated object or $ operators to manipulate the existing. Must
+ * not be {@literal null}.
+ * @param entityClass class that determines the collection to use.
+ * @return the {@link UpdateResult} which lets you access the results of the previous write.
+ * @deprecated since 2.3 in favor of {@link #updateFirst(Query, UpdateDefinition, Class)}.
+ */
+ @Deprecated
+ default Mono updateFirst(Query query, Update update, Class> entityClass) {
+ return updateFirst(query, (UpdateDefinition) update, entityClass);
+ }
/**
* Updates the first object that is found in the specified collection that matches the query document criteria with
@@ -1224,8 +1378,28 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
* not be {@literal null}.
* @param collectionName name of the collection to update the object in. Must not be {@literal null}.
* @return the {@link UpdateResult} which lets you access the results of the previous write.
+ * @since 2.3
*/
- Mono updateFirst(Query query, Update update, String collectionName);
+ Mono updateFirst(Query query, UpdateDefinition update, String collectionName);
+
+ /**
+ * Updates the first object that is found in the specified collection that matches the query document criteria with
+ * the provided updated document.
+ * NOTE: Any additional support for field mapping, versions, etc. is not available due to the lack of
+ * domain type information. Use {@link #updateFirst(Query, Update, Class, String)} to get full type specific support.
+ *
+ * @param query the query document that specifies the criteria used to select a record to be updated. Must not be
+ * {@literal null}.
+ * @param update the update document that contains the updated object or $ operators to manipulate the existing. Must
+ * not be {@literal null}.
+ * @param collectionName name of the collection to update the object in. Must not be {@literal null}.
+ * @return the {@link UpdateResult} which lets you access the results of the previous write.
+ * @deprecated since 2.3 in favor of {@link #updateFirst(Query, UpdateDefinition, String)}.
+ */
+ @Deprecated
+ default Mono updateFirst(Query query, Update update, String collectionName) {
+ return updateFirst(query, (UpdateDefinition) update, collectionName);
+ }
/**
* Updates the first object that is found in the specified collection that matches the query document criteria with
@@ -1240,8 +1414,27 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
* @param entityClass class of the pojo to be operated on. Must not be {@literal null}.
* @param collectionName name of the collection to update the object in. Must not be {@literal null}.
* @return the {@link UpdateResult} which lets you access the results of the previous write.
+ * @since 2.3
*/
- Mono updateFirst(Query query, Update update, Class> entityClass, String collectionName);
+ Mono updateFirst(Query query, UpdateDefinition update, Class> entityClass, String collectionName);
+
+ /**
+ * Updates the first object that is found in the specified collection that matches the query document criteria with
+ * the provided updated document.
+ *
+ * @param query the query document that specifies the criteria used to select a record to be updated. Must not be
+ * {@literal null}.
+ * @param update the update document that contains the updated object or $ operators to manipulate the existing. Must
+ * not be {@literal null}.
+ * @param entityClass class of the pojo to be operated on. Must not be {@literal null}.
+ * @param collectionName name of the collection to update the object in. Must not be {@literal null}.
+ * @return the {@link UpdateResult} which lets you access the results of the previous write.
+ * @deprecated since 2.3 in favor of {@link #updateFirst(Query, UpdateDefinition, Class, String)}.
+ */
+ @Deprecated
+ default Mono updateFirst(Query query, Update update, Class> entityClass, String collectionName) {
+ return updateFirst(query, (UpdateDefinition) update, entityClass, collectionName);
+ }
/**
* Updates all objects that are found in the collection for the entity class that matches the query document criteria
@@ -1253,8 +1446,26 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
* not be {@literal null}.
* @param entityClass class of the pojo to be operated on. Must not be {@literal null}.
* @return the {@link UpdateResult} which lets you access the results of the previous write.
+ * @since 2.3
*/
- Mono updateMulti(Query query, Update update, Class> entityClass);
+ Mono updateMulti(Query query, UpdateDefinition update, Class> entityClass);
+
+ /**
+ * Updates all objects that are found in the collection for the entity class that matches the query document criteria
+ * with the provided updated document.
+ *
+ * @param query the query document that specifies the criteria used to select a record to be updated. Must not be
+ * {@literal null}.
+ * @param update the update document that contains the updated object or $ operators to manipulate the existing. Must
+ * not be {@literal null}.
+ * @param entityClass class of the pojo to be operated on. Must not be {@literal null}.
+ * @return the {@link UpdateResult} which lets you access the results of the previous write.
+ * @deprecated since 2.3 in favor of {@link #updateMulti(Query, UpdateDefinition, Class)}.
+ */
+ @Deprecated
+ default Mono updateMulti(Query query, Update update, Class> entityClass) {
+ return updateMulti(query, (UpdateDefinition) update, entityClass);
+ }
/**
* Updates all objects that are found in the specified collection that matches the query document criteria with the
@@ -1268,8 +1479,28 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
* not be {@literal null}.
* @param collectionName name of the collection to update the object in. Must not be {@literal null}.
* @return the {@link UpdateResult} which lets you access the results of the previous write.
+ * @since 2.3
*/
- Mono updateMulti(Query query, Update update, String collectionName);
+ Mono updateMulti(Query query, UpdateDefinition update, String collectionName);
+
+ /**
+ * Updates all objects that are found in the specified collection that matches the query document criteria with the
+ * provided updated document.
+ * NOTE: Any additional support for field mapping, versions, etc. is not available due to the lack of
+ * domain type information. Use {@link #updateMulti(Query, Update, Class, String)} to get full type specific support.
+ *
+ * @param query the query document that specifies the criteria used to select a record to be updated. Must not be
+ * {@literal null}.
+ * @param update the update document that contains the updated object or $ operators to manipulate the existing. Must
+ * not be {@literal null}.
+ * @param collectionName name of the collection to update the object in. Must not be {@literal null}.
+ * @return the {@link UpdateResult} which lets you access the results of the previous write.
+ * @deprecated since 2.3 in favor of {@link #updateMulti(Query, UpdateDefinition, String)}.
+ */
+ @Deprecated
+ default Mono updateMulti(Query query, Update update, String collectionName) {
+ return updateMulti(query, (UpdateDefinition) update, collectionName);
+ }
/**
* Updates all objects that are found in the collection for the entity class that matches the query document criteria
@@ -1282,8 +1513,27 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
* @param entityClass class of the pojo to be operated on. Must not be {@literal null}.
* @param collectionName name of the collection to update the object in. Must not be {@literal null}.
* @return the {@link UpdateResult} which lets you access the results of the previous write.
+ * @since 2.3
*/
- Mono updateMulti(Query query, Update update, Class> entityClass, String collectionName);
+ Mono updateMulti(Query query, UpdateDefinition update, Class> entityClass, String collectionName);
+
+ /**
+ * Updates all objects that are found in the collection for the entity class that matches the query document criteria
+ * with the provided updated document.
+ *
+ * @param query the query document that specifies the criteria used to select a record to be updated. Must not be
+ * {@literal null}.
+ * @param update the update document that contains the updated object or $ operators to manipulate the existing. Must
+ * not be {@literal null}.
+ * @param entityClass class of the pojo to be operated on. Must not be {@literal null}.
+ * @param collectionName name of the collection to update the object in. Must not be {@literal null}.
+ * @return the {@link UpdateResult} which lets you access the results of the previous write.
+ * @deprecated since 2.3 in favor of {@link #updateMulti(Query, UpdateDefinition, Class, String)}.
+ */
+ @Deprecated
+ default Mono updateMulti(Query query, Update update, Class> entityClass, String collectionName) {
+ return updateMulti(query, (UpdateDefinition) update, entityClass, collectionName);
+ }
/**
* Remove the given object from the collection by id.
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java
index a8944f347..39c83c6ee 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java
@@ -69,7 +69,9 @@ import org.springframework.data.mongodb.core.EntityOperations.AdaptibleEntity;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.AggregationOperationContext;
import org.springframework.data.mongodb.core.aggregation.AggregationOptions;
+import org.springframework.data.mongodb.core.aggregation.AggregationUpdate;
import org.springframework.data.mongodb.core.aggregation.PrefixingDelegatingAggregationOperationContext;
+import org.springframework.data.mongodb.core.aggregation.RelaxedTypeBasedAggregationOperationContext;
import org.springframework.data.mongodb.core.aggregation.TypeBasedAggregationOperationContext;
import org.springframework.data.mongodb.core.aggregation.TypedAggregation;
import org.springframework.data.mongodb.core.convert.DbRefResolver;
@@ -105,7 +107,6 @@ import org.springframework.data.mongodb.core.query.Meta;
import org.springframework.data.mongodb.core.query.Meta.CursorOption;
import org.springframework.data.mongodb.core.query.NearQuery;
import org.springframework.data.mongodb.core.query.Query;
-import org.springframework.data.mongodb.core.query.Update;
import org.springframework.data.mongodb.core.query.UpdateDefinition;
import org.springframework.data.mongodb.core.query.UpdateDefinition.ArrayFilter;
import org.springframework.data.mongodb.core.validation.Validator;
@@ -1125,34 +1126,35 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
/*
* (non-Javadoc)
- * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#findAndModify(org.springframework.data.mongodb.core.query.Query, org.springframework.data.mongodb.core.query.Update, java.lang.Class)
+ * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#findAndModify(org.springframework.data.mongodb.core.query.Query, org.springframework.data.mongodb.core.query.UpdateDefinition, java.lang.Class)
*/
- public Mono findAndModify(Query query, Update update, Class entityClass) {
+ public Mono findAndModify(Query query, UpdateDefinition update, Class entityClass) {
return findAndModify(query, update, new FindAndModifyOptions(), entityClass, getCollectionName(entityClass));
}
/*
* (non-Javadoc)
- * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#findAndModify(org.springframework.data.mongodb.core.query.Query, org.springframework.data.mongodb.core.query.Update, java.lang.Class, java.lang.String)
+ * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#findAndModify(org.springframework.data.mongodb.core.query.Query, org.springframework.data.mongodb.core.query.UpdateDefinition, java.lang.Class, java.lang.String)
*/
- public Mono findAndModify(Query query, Update update, Class entityClass, String collectionName) {
+ public Mono findAndModify(Query query, UpdateDefinition update, Class entityClass, String collectionName) {
return findAndModify(query, update, new FindAndModifyOptions(), entityClass, collectionName);
}
/*
* (non-Javadoc)
- * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#findAndModify(org.springframework.data.mongodb.core.query.Query, org.springframework.data.mongodb.core.query.Update, org.springframework.data.mongodb.core.FindAndModifyOptions, java.lang.Class)
+ * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#findAndModify(org.springframework.data.mongodb.core.query.Query, org.springframework.data.mongodb.core.query.UpdateDefinition, org.springframework.data.mongodb.core.FindAndModifyOptions, java.lang.Class)
*/
- public Mono findAndModify(Query query, Update update, FindAndModifyOptions options, Class entityClass) {
+ public Mono findAndModify(Query query, UpdateDefinition update, FindAndModifyOptions options,
+ Class entityClass) {
return findAndModify(query, update, options, entityClass, getCollectionName(entityClass));
}
/*
* (non-Javadoc)
- * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#findAndModify(org.springframework.data.mongodb.core.query.Query, org.springframework.data.mongodb.core.query.Update, org.springframework.data.mongodb.core.FindAndModifyOptions, java.lang.Class, java.lang.String)
+ * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#findAndModify(org.springframework.data.mongodb.core.query.Query, org.springframework.data.mongodb.core.query.UpdateDefinition, org.springframework.data.mongodb.core.FindAndModifyOptions, java.lang.Class, java.lang.String)
*/
- public Mono findAndModify(Query query, Update update, FindAndModifyOptions options, Class entityClass,
- String collectionName) {
+ public Mono findAndModify(Query query, UpdateDefinition update, FindAndModifyOptions options,
+ Class entityClass, String collectionName) {
Assert.notNull(options, "Options must not be null! ");
@@ -1165,7 +1167,6 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
if (!optionsToUse.getCollation().isPresent()) {
operations.forType(entityClass).getCollation(query).ifPresent(optionsToUse::collation);
- ;
}
return doFindAndModify(collectionName, query.getQueryObject(), query.getFieldsObject(),
@@ -1687,73 +1688,75 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
/*
* (non-Javadoc)
- * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#upsert(org.springframework.data.mongodb.core.query.Query, org.springframework.data.mongodb.core.query.Update, java.lang.Class)
+ * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#upsert(org.springframework.data.mongodb.core.query.Query, org.springframework.data.mongodb.core.query.UpdateDefinition, java.lang.Class)
*/
- public Mono upsert(Query query, Update update, Class> entityClass) {
+ public Mono upsert(Query query, UpdateDefinition update, Class> entityClass) {
return doUpdate(getCollectionName(entityClass), query, update, entityClass, true, false);
}
/*
* (non-Javadoc)
- * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#upsert(org.springframework.data.mongodb.core.query.Query, org.springframework.data.mongodb.core.query.Update, java.lang.String)
+ * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#upsert(org.springframework.data.mongodb.core.query.Query, org.springframework.data.mongodb.core.query.UpdateDefinition, java.lang.String)
*/
- public Mono upsert(Query query, Update update, String collectionName) {
+ public Mono upsert(Query query, UpdateDefinition update, String collectionName) {
return doUpdate(collectionName, query, update, null, true, false);
}
/*
* (non-Javadoc)
- * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#upsert(org.springframework.data.mongodb.core.query.Query, org.springframework.data.mongodb.core.query.Update, java.lang.Class, java.lang.String)
+ * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#upsert(org.springframework.data.mongodb.core.query.Query, org.springframework.data.mongodb.core.query.UpdateDefinition, java.lang.Class, java.lang.String)
*/
- public Mono upsert(Query query, Update update, Class> entityClass, String collectionName) {
+ public Mono upsert(Query query, UpdateDefinition update, Class> entityClass, String collectionName) {
return doUpdate(collectionName, query, update, entityClass, true, false);
}
/*
* (non-Javadoc))
- * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#updateFirst(org.springframework.data.mongodb.core.query.Query, org.springframework.data.mongodb.core.query.Update, java.lang.Class)
+ * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#updateFirst(org.springframework.data.mongodb.core.query.Query, org.springframework.data.mongodb.core.query.UpdateDefinition, java.lang.Class)
*/
- public Mono updateFirst(Query query, Update update, Class> entityClass) {
+ public Mono updateFirst(Query query, UpdateDefinition update, Class> entityClass) {
return doUpdate(getCollectionName(entityClass), query, update, entityClass, false, false);
}
/*
* (non-Javadoc)
- * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#updateFirst(org.springframework.data.mongodb.core.query.Query, org.springframework.data.mongodb.core.query.Update, java.lang.String)
+ * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#updateFirst(org.springframework.data.mongodb.core.query.Query, org.springframework.data.mongodb.core.query.UpdateDefinition, java.lang.String)
*/
- public Mono updateFirst(Query query, Update update, String collectionName) {
+ public Mono updateFirst(Query query, UpdateDefinition update, String collectionName) {
return doUpdate(collectionName, query, update, null, false, false);
}
/*
* (non-Javadoc)
- * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#updateFirst(org.springframework.data.mongodb.core.query.Query, org.springframework.data.mongodb.core.query.Update, java.lang.Class, java.lang.String)
+ * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#updateFirst(org.springframework.data.mongodb.core.query.Query, org.springframework.data.mongodb.core.query.UpdateDefinition, java.lang.Class, java.lang.String)
*/
- public Mono updateFirst(Query query, Update update, Class> entityClass, String collectionName) {
+ public Mono updateFirst(Query query, UpdateDefinition update, Class> entityClass,
+ String collectionName) {
return doUpdate(collectionName, query, update, entityClass, false, false);
}
/*
* (non-Javadoc)
- * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#updateMulti(org.springframework.data.mongodb.core.query.Query, org.springframework.data.mongodb.core.query.Update, java.lang.Class)
+ * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#updateMulti(org.springframework.data.mongodb.core.query.Query, org.springframework.data.mongodb.core.query.UpdateDefinition, java.lang.Class)
*/
- public Mono updateMulti(Query query, Update update, Class> entityClass) {
+ public Mono updateMulti(Query query, UpdateDefinition update, Class> entityClass) {
return doUpdate(getCollectionName(entityClass), query, update, entityClass, false, true);
}
/*
* (non-Javadoc)
- * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#updateMulti(org.springframework.data.mongodb.core.query.Query, org.springframework.data.mongodb.core.query.Update, java.lang.String)
+ * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#updateMulti(org.springframework.data.mongodb.core.query.Query, org.springframework.data.mongodb.core.query.UpdateDefinition, java.lang.String)
*/
- public Mono updateMulti(Query query, Update update, String collectionName) {
+ public Mono updateMulti(Query query, UpdateDefinition update, String collectionName) {
return doUpdate(collectionName, query, update, null, false, true);
}
/*
* (non-Javadoc)
- * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#updateMulti(org.springframework.data.mongodb.core.query.Query, org.springframework.data.mongodb.core.query.Update, java.lang.Class, java.lang.String)
+ * @see org.springframework.data.mongodb.core.ReactiveMongoOperations#updateMulti(org.springframework.data.mongodb.core.query.Query, org.springframework.data.mongodb.core.query.UpdateDefinition, java.lang.Class, java.lang.String)
*/
- public Mono updateMulti(Query query, Update update, Class> entityClass, String collectionName) {
+ public Mono updateMulti(Query query, UpdateDefinition update, Class> entityClass,
+ String collectionName) {
return doUpdate(collectionName, query, update, entityClass, false, true);
}
@@ -1767,54 +1770,86 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
}
MongoPersistentEntity> entity = entityClass == null ? null : getPersistentEntity(entityClass);
+ increaseVersionForUpdateIfNecessary(entity, update);
- Flux result = execute(collectionName, collection -> {
+ Document queryObj = queryMapper.getMappedObject(query.getQueryObject(), entity);
- increaseVersionForUpdateIfNecessary(entity, update);
+ UpdateOptions updateOptions = new UpdateOptions().upsert(upsert);
+ operations.forType(entityClass).getCollation(query) //
+ .map(Collation::toMongoCollation) //
+ .ifPresent(updateOptions::collation);
- Document queryObj = queryMapper.getMappedObject(query.getQueryObject(), entity);
- Document updateObj = update == null ? new Document()
- : updateMapper.getMappedObject(update.getUpdateObject(), entity);
+ if (update.hasArrayFilters()) {
+ updateOptions.arrayFilters(update.getArrayFilters().stream().map(ArrayFilter::asDocument)
+ .map(it -> queryMapper.getMappedObject(it, entity)).collect(Collectors.toList()));
+ }
- if (LOGGER.isDebugEnabled()) {
- LOGGER.debug(String.format("Calling update using query: %s and update: %s in collection: %s",
- serializeToJsonSafely(queryObj), serializeToJsonSafely(updateObj), collectionName));
- }
+ if (multi && update.isIsolated() && !queryObj.containsKey("$isolated")) {
+ queryObj.put("$isolated", 1);
+ }
- MongoAction mongoAction = new MongoAction(writeConcern, MongoActionOperation.UPDATE, collectionName, entityClass,
- updateObj, queryObj);
- WriteConcern writeConcernToUse = prepareWriteConcern(mongoAction);
- MongoCollection collectionToUse = prepareCollection(collection, writeConcernToUse);
+ Flux result = Flux.empty();
- UpdateOptions updateOptions = new UpdateOptions().upsert(upsert);
- operations.forType(entityClass).getCollation(query) //
- .map(Collation::toMongoCollation) //
- .ifPresent(updateOptions::collation);
+ if (update instanceof AggregationUpdate) {
- if (update.hasArrayFilters()) {
- updateOptions.arrayFilters(update.getArrayFilters().stream().map(ArrayFilter::asDocument)
- .map(it -> queryMapper.getMappedObject(it, entity)).collect(Collectors.toList()));
- }
+ AggregationOperationContext context = entityClass != null
+ ? new RelaxedTypeBasedAggregationOperationContext(entityClass, mappingContext, queryMapper)
+ : Aggregation.DEFAULT_CONTEXT;
- if (!UpdateMapper.isUpdateObject(updateObj)) {
+ AggregationUpdate aUppdate = ((AggregationUpdate) update);
+ List pipeline = new AggregationUtil(queryMapper, mappingContext).createPipeline(aUppdate, context);
- ReplaceOptions replaceOptions = new ReplaceOptions();
- replaceOptions.upsert(updateOptions.isUpsert());
- replaceOptions.collation(updateOptions.getCollation());
+ result = execute(collectionName, collection -> {
- return collectionToUse.replaceOne(queryObj, updateObj, replaceOptions);
- }
- if (multi) {
- return collectionToUse.updateMany(queryObj, updateObj, updateOptions);
- }
- return collectionToUse.updateOne(queryObj, updateObj, updateOptions);
- }).doOnNext(updateResult -> {
+ MongoAction mongoAction = new MongoAction(writeConcern, MongoActionOperation.UPDATE, collectionName,
+ entityClass, update.getUpdateObject(), queryObj);
+ WriteConcern writeConcernToUse = prepareWriteConcern(mongoAction);
+
+ collection = writeConcernToUse != null ? collection.withWriteConcern(writeConcernToUse) : collection;
+
+ if (multi) {
+ return collection.updateMany(queryObj, pipeline, updateOptions);
+ }
+
+ return collection.updateOne(queryObj, pipeline, updateOptions);
+ });
+ } else {
+
+ result = execute(collectionName, collection -> {
+
+ Document updateObj = update == null ? new Document()
+ : updateMapper.getMappedObject(update.getUpdateObject(), entity);
+
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debug(String.format("Calling update using query: %s and update: %s in collection: %s",
+ serializeToJsonSafely(queryObj), serializeToJsonSafely(updateObj), collectionName));
+ }
+
+ MongoAction mongoAction = new MongoAction(writeConcern, MongoActionOperation.UPDATE, collectionName,
+ entityClass, updateObj, queryObj);
+ WriteConcern writeConcernToUse = prepareWriteConcern(mongoAction);
+ MongoCollection collectionToUse = prepareCollection(collection, writeConcernToUse);
+
+ if (!UpdateMapper.isUpdateObject(updateObj)) {
+
+ ReplaceOptions replaceOptions = new ReplaceOptions();
+ replaceOptions.upsert(updateOptions.isUpsert());
+ replaceOptions.collation(updateOptions.getCollation());
+
+ return collectionToUse.replaceOne(queryObj, updateObj, replaceOptions);
+ }
+ if (multi) {
+ return collectionToUse.updateMany(queryObj, updateObj, updateOptions);
+ }
+ return collectionToUse.updateOne(queryObj, updateObj, updateOptions);
+ });
+ }
+
+ result = result.doOnNext(updateResult -> {
if (entity != null && entity.hasVersionProperty() && !multi) {
if (updateResult.wasAcknowledged() && updateResult.getMatchedCount() == 0) {
- Document queryObj = query == null ? new Document()
- : queryMapper.getMappedObject(query.getQueryObject(), entity);
Document updateObj = update == null ? new Document()
: updateMapper.getMappedObject(update.getUpdateObject(), entity);
if (containsVersionProperty(queryObj, entity))
@@ -2542,16 +2577,26 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
}
protected Mono doFindAndModify(String collectionName, Document query, Document fields, Document sort,
- Class entityClass, Update update, FindAndModifyOptions options) {
+ Class entityClass, UpdateDefinition update, FindAndModifyOptions options) {
MongoPersistentEntity> entity = mappingContext.getPersistentEntity(entityClass);
+ increaseVersionForUpdateIfNecessary(entity, update);
return Mono.defer(() -> {
- increaseVersionForUpdateIfNecessary(entity, update);
-
Document mappedQuery = queryMapper.getMappedObject(query, entity);
- Document mappedUpdate = updateMapper.getMappedObject(update.getUpdateObject(), entity);
+
+ Object mappedUpdate = new Document();
+ if (update instanceof AggregationUpdate) {
+
+ AggregationOperationContext context = entityClass != null
+ ? new RelaxedTypeBasedAggregationOperationContext(entityClass, mappingContext, queryMapper)
+ : Aggregation.DEFAULT_CONTEXT;
+
+ mappedUpdate = new AggregationUtil(queryMapper, mappingContext).createPipeline((Aggregation) update, context);
+ } else {
+ mappedUpdate = updateMapper.getMappedObject(update.getUpdateObject(), entity);
+ }
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(String.format(
@@ -2928,7 +2973,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
private final Document query;
private final Document fields;
private final Document sort;
- private final Document update;
+ private final Object update;
private final List arrayFilters;
private final FindAndModifyOptions options;
@@ -2947,7 +2992,12 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
FindOneAndUpdateOptions findOneAndUpdateOptions = convertToFindOneAndUpdateOptions(options, fields, sort,
arrayFilters);
- return collection.findOneAndUpdate(query, update, findOneAndUpdateOptions);
+ if (update instanceof Document) {
+ return collection.findOneAndUpdate(query, (Document) update, findOneAndUpdateOptions);
+ } else if (update instanceof List) {
+ return collection.findOneAndUpdate(query, (List) update, findOneAndUpdateOptions);
+ }
+ return Flux.error(new IllegalArgumentException("doh - that does not work"));
}
private static FindOneAndUpdateOptions convertToFindOneAndUpdateOptions(FindAndModifyOptions options,
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveUpdateOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveUpdateOperation.java
index be3fa10e2..7739f9980 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveUpdateOperation.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveUpdateOperation.java
@@ -15,6 +15,7 @@
*/
package org.springframework.data.mongodb.core;
+import org.springframework.data.mongodb.core.query.UpdateDefinition;
import reactor.core.publisher.Mono;
import org.springframework.data.mongodb.core.query.Query;
@@ -123,8 +124,22 @@ public interface ReactiveUpdateOperation {
* @param update must not be {@literal null}.
* @return new instance of {@link TerminatingUpdate}. Never {@literal null}.
* @throws IllegalArgumentException if update is {@literal null}.
+ * @since 2.3
*/
- TerminatingUpdate apply(org.springframework.data.mongodb.core.query.Update update);
+ TerminatingUpdate apply(org.springframework.data.mongodb.core.query.UpdateDefinition update);
+
+ /**
+ * Set the {@link org.springframework.data.mongodb.core.query.Update} to be applied.
+ *
+ * @param update must not be {@literal null}.
+ * @return new instance of {@link TerminatingUpdate}. Never {@literal null}.
+ * @throws IllegalArgumentException if update is {@literal null}.
+ * @deprecated since 2.3 in favor of {@link #apply(UpdateDefinition)}.
+ */
+ @Deprecated
+ default TerminatingUpdate apply(org.springframework.data.mongodb.core.query.Update update) {
+ return apply((UpdateDefinition) update);
+ }
/**
* Specify {@code replacement} object.
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveUpdateOperationSupport.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveUpdateOperationSupport.java
index 6030606b1..22c1ad689 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveUpdateOperationSupport.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveUpdateOperationSupport.java
@@ -63,7 +63,7 @@ class ReactiveUpdateOperationSupport implements ReactiveUpdateOperation {
@NonNull ReactiveMongoTemplate template;
@NonNull Class> domainType;
Query query;
- org.springframework.data.mongodb.core.query.Update update;
+ org.springframework.data.mongodb.core.query.UpdateDefinition update;
@Nullable String collection;
@Nullable FindAndModifyOptions findAndModifyOptions;
@Nullable FindAndReplaceOptions findAndReplaceOptions;
@@ -72,10 +72,10 @@ class ReactiveUpdateOperationSupport implements ReactiveUpdateOperation {
/*
* (non-Javadoc)
- * @see org.springframework.data.mongodb.core.ReactiveUpdateOperation.UpdateWithUpdate#apply(org.springframework.data.mongodb.core.query.Update)
+ * @see org.springframework.data.mongodb.core.ReactiveUpdateOperation.UpdateWithUpdate#apply(org.springframework.data.mongodb.core.query.UpdateDefinition)
*/
@Override
- public TerminatingUpdate apply(org.springframework.data.mongodb.core.query.Update update) {
+ public TerminatingUpdate apply(org.springframework.data.mongodb.core.query.UpdateDefinition update) {
Assert.notNull(update, "Update must not be null!");
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/Aggregation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/Aggregation.java
index 32f2f6e9a..70d2987ca 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/Aggregation.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/Aggregation.java
@@ -115,6 +115,17 @@ public class Aggregation {
return new Aggregation(operations);
}
+ /**
+ * Creates a new {@link AggregationUpdate} from the given {@link AggregationOperation}s.
+ *
+ * @param operations can be {@literal empty} but must not be {@literal null}.
+ * @return new instance of {@link AggregationUpdate}.
+ * @since 2.3
+ */
+ public static AggregationUpdate newUpdate(AggregationOperation... operations) {
+ return AggregationUpdate.from(Arrays.asList(operations));
+ }
+
/**
* Returns a copy of this {@link Aggregation} with the given {@link AggregationOptions} set. Note that options are
* supported in MongoDB version 2.6+.
@@ -181,13 +192,12 @@ public class Aggregation {
/**
* Creates a new {@link Aggregation} from the given {@link AggregationOperation}s.
*
- * @param aggregationOperations must not be {@literal null} or empty.
+ * @param aggregationOperations must not be {@literal null}.
* @param options must not be {@literal null} or empty.
*/
protected Aggregation(List aggregationOperations, AggregationOptions options) {
Assert.notNull(aggregationOperations, "AggregationOperations must not be null!");
- Assert.isTrue(!aggregationOperations.isEmpty(), "At least one AggregationOperation has to be provided");
Assert.notNull(options, "AggregationOptions must not be null!");
// check $out is the last operation if it exists
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationUpdate.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationUpdate.java
new file mode 100644
index 000000000..7cc230466
--- /dev/null
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationUpdate.java
@@ -0,0 +1,332 @@
+/*
+ * Copyright 2019 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.aggregation;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.bson.Document;
+import org.springframework.data.mongodb.core.query.Query;
+import org.springframework.data.mongodb.core.query.SerializationUtils;
+import org.springframework.data.mongodb.core.query.Update;
+import org.springframework.data.mongodb.core.query.UpdateDefinition;
+import org.springframework.lang.Nullable;
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
+
+/**
+ * Abstraction for an {@code db.collection.update()} using an aggregation pipeline for a more expressive update
+ * statement expressing conditional updates based on current field values or updating one field using the value of
+ * another field(s).
+ *
+ *
+ * AggregationUpdate update = AggregationUpdate.update().set("average")
+ * .toValue(ArithmeticOperators.valueOf("tests").avg()).set("grade")
+ * .toValue(ConditionalOperators
+ * .switchCases(CaseOperator.when(Gte.valueOf("average").greaterThanEqualToValue(90)).then("A"),
+ * CaseOperator.when(Gte.valueOf("average").greaterThanEqualToValue(80)).then("B"),
+ * CaseOperator.when(Gte.valueOf("average").greaterThanEqualToValue(70)).then("C"),
+ * CaseOperator.when(Gte.valueOf("average").greaterThanEqualToValue(60)).then("D"))
+ * .defaultTo("F"));
+ *
+ *
+ * The above sample is equivalent to the JSON update statement
+ *
+ *
+ * db.collection.update(
+ * { },
+ * [
+ * { $set: { average : { $avg: "$tests" } } },
+ * { $set: { grade: { $switch: {
+ * branches: [
+ * { case: { $gte: [ "$average", 90 ] }, then: "A" },
+ * { case: { $gte: [ "$average", 80 ] }, then: "B" },
+ * { case: { $gte: [ "$average", 70 ] }, then: "C" },
+ * { case: { $gte: [ "$average", 60 ] }, then: "D" }
+ * ],
+ * default: "F"
+ * } } } }
+ * ],
+ * { multi: true }
+ * )
+ *
+ *
+ * @author Christoph Strobl
+ * @see MongoDB
+ * Reference Documentation
+ * @since 2.3
+ */
+public class AggregationUpdate extends Aggregation implements UpdateDefinition {
+
+ private boolean isolated = false;
+ private Set keysTouched = new HashSet<>();
+
+ /**
+ * Create new {@link AggregationUpdate}.
+ */
+ public AggregationUpdate() {
+ this(new ArrayList<>());
+ }
+
+ /**
+ * Create new {@link AggregationUpdate} with the given aggregation pipeline to apply.
+ *
+ * @param pipeline must not be {@literal null}.
+ */
+ private AggregationUpdate(List pipeline) {
+
+ super(pipeline);
+
+ for (AggregationOperation operation : pipeline) {
+ if (operation instanceof FieldsExposingAggregationOperation) {
+ ((FieldsExposingAggregationOperation) operation).getFields().forEach(it -> {
+ if (it instanceof Field) {
+ keysTouched.add(((Field) it).getName());
+ } else {
+ keysTouched.add(it.toString());
+ }
+ });
+ }
+ }
+ }
+
+ /**
+ * Start defining the update pipeline to execute.
+ *
+ * @return new instance of {@link AggregationUpdate}.
+ */
+ public static AggregationUpdate update() {
+ return new AggregationUpdate();
+ }
+
+ /**
+ * Create a new AggregationUpdate from the given {@link AggregationOperation}s.
+ *
+ * @return new instance of {@link AggregationUpdate}.
+ */
+ public static AggregationUpdate from(List pipeline) {
+ return new AggregationUpdate(pipeline);
+ }
+
+ /**
+ * Adds new fields to documents. {@code $set} outputs documents that contain all existing fields from the input
+ * documents and newly added fields.
+ *
+ * @param setOperation must not be {@literal null}.
+ * @return this.
+ * @see $set Aggregation Reference
+ */
+ public AggregationUpdate set(SetOperation setOperation) {
+
+ Assert.notNull(setOperation, "SetOperation must not be null!");
+
+ setOperation.getFields().forEach(it -> {
+ if (it instanceof Field) {
+ keysTouched.add(((Field) it).getName());
+ } else {
+ keysTouched.add(it.toString());
+ }
+ });
+ operations.add(setOperation);
+ return this;
+ }
+
+ /**
+ * {@code $unset} removes/excludes fields from documents.
+ *
+ * @param unsetOperation must not be {@literal null}.
+ * @return this.
+ * @see $unset Aggregation
+ * Reference
+ */
+ public AggregationUpdate unset(UnsetOperation unsetOperation) {
+
+ Assert.notNull(unsetOperation, "UnsetOperation must not be null!");
+
+ operations.add(unsetOperation);
+ keysTouched.addAll(unsetOperation.removedFieldNames());
+ return this;
+ }
+
+ /**
+ * {@code $replaceWith} replaces the input document with the specified document. The operation replaces all existing
+ * fields in the input document, including the _id field.
+ *
+ * @param replaceWithOperation
+ * @return this.
+ * @see $replaceWith Aggregation
+ * Reference
+ */
+ public AggregationUpdate replaceWith(ReplaceWithOperation replaceWithOperation) {
+
+ Assert.notNull(replaceWithOperation, "ReplaceWithOperation must not be null!");
+ operations.add(replaceWithOperation);
+ return this;
+ }
+
+ /**
+ * {@code $replaceWith} replaces the input document with the value.
+ *
+ * @param value must not be {@literal null}.
+ * @return this.
+ */
+ public AggregationUpdate replaceWith(Object value) {
+
+ Assert.notNull(value, "Value must not be null!");
+ return replaceWith(ReplaceWithOperation.replaceWithValue(value));
+ }
+
+ /**
+ * Fluent API variant for {@code $set} adding a single {@link SetOperation pipeline operation} every time. To update
+ * multiple fields within one {@link SetOperation} use {@link #set(SetOperation)}.
+ *
+ * @param key must not be {@literal null}.
+ * @return new instance of {@link SetValueAppender}.
+ * @see #set(SetOperation)
+ */
+ public SetValueAppender set(String key) {
+
+ Assert.notNull(key, "Key must not be null!");
+
+ return new SetValueAppender() {
+
+ @Override
+ public AggregationUpdate toValue(@Nullable Object value) {
+ return set(SetOperation.builder().set(key).toValue(value));
+ }
+
+ @Override
+ public AggregationUpdate toValueOf(Object value) {
+
+ Assert.notNull(value, "Value must not be null!");
+ return set(SetOperation.builder().set(key).toValueOf(value));
+ }
+ };
+ }
+
+ /**
+ * Short for {@link #unset(UnsetOperation)}.
+ *
+ * @param keys
+ * @return
+ */
+ public AggregationUpdate unset(String... keys) {
+
+ Assert.notNull(keys, "Keys must not be null!");
+ Assert.noNullElements(keys, "Keys must not contain null elements.");
+
+ return unset(new UnsetOperation(Arrays.stream(keys).map(Fields::field).collect(Collectors.toList())));
+ }
+
+ /**
+ * Prevents a write operation that affects multiple documents from yielding to other reads or writes
+ * once the first document is written.
+ * Use with {@link org.springframework.data.mongodb.core.MongoOperations#updateMulti(Query, Update, Class)}.
+ *
+ * @return never {@literal null}.
+ */
+ public AggregationUpdate isolated() {
+
+ isolated = true;
+ return this;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.mongodb.core.query.UpdateDefinition#isIsolated()
+ */
+ @Override
+ public Boolean isIsolated() {
+ return isolated;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.mongodb.core.query.UpdateDefinition#getUpdateObject()
+ */
+ @Override
+ public Document getUpdateObject() {
+ return new Document("", toPipeline(Aggregation.DEFAULT_CONTEXT));
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.mongodb.core.query.UpdateDefinition#modifies(java.lang.String)
+ */
+ @Override
+ public boolean modifies(String key) {
+ return keysTouched.contains(key);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.mongodb.core.query.UpdateDefinition#inc(java.lang.String)
+ */
+ @Override
+ public void inc(String key) {
+ set(new SetOperation(key, ArithmeticOperators.valueOf(key).add(1)));
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.mongodb.core.query.UpdateDefinition#getArrayFilters()
+ */
+ @Override
+ public List getArrayFilters() {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public String toString() {
+
+ String target = "[\n";
+ target += StringUtils.collectionToDelimitedString(toPipeline(Aggregation.DEFAULT_CONTEXT).stream()
+ .map(SerializationUtils::serializeToJsonSafely).collect(Collectors.toList()), ",\n");
+ target += "\n]";
+ return target;
+ }
+
+ /**
+ * Fluent API AggregationUpdate builder.
+ *
+ * @author Christoph Strobl
+ * @since 2.3
+ */
+ public interface SetValueAppender {
+
+ /**
+ * Define the target value as is.
+ *
+ * @param value can be {@literal null}.
+ * @return never {@literal null}.
+ */
+ AggregationUpdate toValue(@Nullable Object value);
+
+ /**
+ * Define the target value as value, an {@link AggregationExpression} or a {@link Field} reference.
+ *
+ * @param value can be {@literal null}.
+ * @return never {@literal null}.
+ */
+ AggregationUpdate toValueOf(Object value);
+ }
+}
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/RelaxedTypeBasedAggregationOperationContext.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/RelaxedTypeBasedAggregationOperationContext.java
new file mode 100644
index 000000000..dd96750f7
--- /dev/null
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/RelaxedTypeBasedAggregationOperationContext.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2019 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.aggregation;
+
+import org.springframework.data.mapping.context.InvalidPersistentPropertyPath;
+import org.springframework.data.mapping.context.MappingContext;
+import org.springframework.data.mongodb.core.aggregation.ExposedFields.DirectFieldReference;
+import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedField;
+import org.springframework.data.mongodb.core.aggregation.ExposedFields.FieldReference;
+import org.springframework.data.mongodb.core.convert.QueryMapper;
+import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
+import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
+
+/**
+ * A {@link TypeBasedAggregationOperationContext} with less restrictive field reference handling, suppressing
+ * {@link InvalidPersistentPropertyPath} exceptions when resolving mapped field names.
+ *
+ * @author Christoph Strobl
+ * @since 2.3
+ */
+public class RelaxedTypeBasedAggregationOperationContext extends TypeBasedAggregationOperationContext {
+
+ /**
+ * Creates a new {@link TypeBasedAggregationOperationContext} for the given type, {@link MappingContext} and
+ * {@link QueryMapper}.
+ *
+ * @param type must not be {@literal null}.
+ * @param mappingContext must not be {@literal null}.
+ * @param mapper must not be {@literal null}.
+ */
+ public RelaxedTypeBasedAggregationOperationContext(Class> type,
+ MappingContext extends MongoPersistentEntity>, MongoPersistentProperty> mappingContext, QueryMapper mapper) {
+ super(type, mappingContext, mapper);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.mongodb.core.aggregation.TypeBasedAggregationOperationContext#getReferenceFor(rg.springframework.data.mongodb.core.aggregation.Field)
+ */
+ @Override
+ protected FieldReference getReferenceFor(Field field) {
+
+ try {
+ return super.getReferenceFor(field);
+ } catch (InvalidPersistentPropertyPath e) {
+ return new DirectFieldReference(new ExposedField(field, true));
+ }
+ }
+}
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ReplaceRootOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ReplaceRootOperation.java
index cab2e4ff0..00c7bbc22 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ReplaceRootOperation.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ReplaceRootOperation.java
@@ -33,7 +33,8 @@ import org.springframework.util.Assert;
* @author Mark Paluch
* @author Christoph Strobl
* @since 1.10
- * @see MongoDB Aggregation Framework: $replaceRoot
+ * @see MongoDB Aggregation
+ * Framework: $replaceRoot
*/
public class ReplaceRootOperation implements FieldsExposingAggregationOperation {
@@ -82,7 +83,7 @@ public class ReplaceRootOperation implements FieldsExposingAggregationOperation
*/
@Override
public Document toDocument(AggregationOperationContext context) {
- return new Document("$replaceRoot", new Document("newRoot", replacement.toDocumentExpression(context)));
+ return new Document("$replaceRoot", new Document("newRoot", getReplacement().toDocumentExpression(context)));
}
/* (non-Javadoc)
@@ -93,6 +94,16 @@ public class ReplaceRootOperation implements FieldsExposingAggregationOperation
return ExposedFields.from();
}
+ /**
+ * Obtain the {@link Replacement}.
+ *
+ * @return never {@literal null}.
+ * @since 2.3
+ */
+ protected Replacement getReplacement() {
+ return replacement;
+ }
+
/**
* Builder for {@link ReplaceRootOperation}.
*
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ReplaceWithOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ReplaceWithOperation.java
new file mode 100644
index 000000000..81454326b
--- /dev/null
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ReplaceWithOperation.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2019 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.aggregation;
+
+import java.util.Collection;
+import java.util.stream.Collectors;
+
+import org.bson.Document;
+import org.springframework.util.Assert;
+
+/**
+ * Encapsulates the aggregation framework {@code $replaceRoot}-operation.
+ * The operation replaces all existing fields including the {@code id} field with @{code $replaceWith}. This way it is
+ * possible to promote an embedded document to the top-level or specify a new document.
+ *
+ * @author Christoph Strobl
+ * @since 2.3
+ * @see MongoDB Aggregation
+ * Framework: $replaceWith
+ */
+public class ReplaceWithOperation extends ReplaceRootOperation {
+
+ /**
+ * Creates new instance of {@link ReplaceWithOperation}.
+ *
+ * @param replacement must not be {@literal null}.
+ */
+ public ReplaceWithOperation(Replacement replacement) {
+ super(replacement);
+ }
+
+ /**
+ * Creates new instance of {@link ReplaceWithOperation}.
+ *
+ * @param value must not be {@literal null}.
+ * @return new instance of {@link ReplaceWithOperation}.
+ */
+ public static ReplaceWithOperation replaceWithValue(Object value) {
+ return new ReplaceWithOperation((ctx) -> value);
+ }
+
+ /**
+ * Creates new instance of {@link ReplaceWithOperation} treating a given {@link String} {@literal value} as a
+ * {@link Field field reference}.
+ *
+ * @param value must not be {@literal null}.
+ * @return
+ */
+ public static ReplaceWithOperation replaceWithValueOf(Object value) {
+
+ Assert.notNull(value, "Value must not be null!");
+ return new ReplaceWithOperation((ctx) -> {
+
+ Object target = value instanceof String ? Fields.field((String) value) : value;
+ return computeValue(target, ctx);
+ });
+ }
+
+ private static Object computeValue(Object value, AggregationOperationContext context) {
+
+ if (value instanceof Field) {
+ return context.getReference((Field) value).toString();
+ }
+ if (value instanceof AggregationExpression) {
+ return ((AggregationExpression) value).toDocument(context);
+ }
+ if (value instanceof Collection) {
+ return ((Collection) value).stream().map(it -> computeValue(it, context)).collect(Collectors.toList());
+ }
+
+ return value;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.mongodb.core.aggregation.AggregationOperation#toDocument(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
+ */
+ @Override
+ public Document toDocument(AggregationOperationContext context) {
+ return context.getMappedObject(new Document("$replaceWith", getReplacement().toDocumentExpression(context)));
+ }
+}
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/SetOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/SetOperation.java
new file mode 100644
index 000000000..0c7933eec
--- /dev/null
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/SetOperation.java
@@ -0,0 +1,250 @@
+/*
+ * Copyright 2019 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.aggregation;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.stream.Collectors;
+
+import org.bson.Document;
+import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedField;
+import org.springframework.data.mongodb.core.aggregation.FieldsExposingAggregationOperation.InheritsFieldsAggregationOperation;
+import org.springframework.data.mongodb.core.aggregation.SetOperation.FieldAppender.ValueAppender;
+import org.springframework.lang.Nullable;
+
+/**
+ * Adds new fields to documents. {@code $set} outputs documents that contain all existing fields from the input
+ * documents and newly added fields.
+ *
+ *
+ * SetOperation.set("totalHomework").toValue("A+").and().set("totalQuiz").toValue("B-")
+ *
+ *
+ * @author Christoph Strobl
+ * @since 2.3
+ * @see MongoDB Aggregation Framework:
+ * $set
+ */
+public class SetOperation implements InheritsFieldsAggregationOperation {
+
+ private Map