Skip to content

Commit 07b52d0

Browse files
DATAMONGO-2331 - Add support for Update with an aggregation pipeline.
Now the update methods exposed by (Reactive)MongoOperations also accept an Aggregation Pipeline via AggregationUpdate. The update can consist of the following stages: * AggregationUpdate.set(...).toValue(...) -> $set : { ... } * AggregationUpdate.unset(...) -> $unset : [ ... ] * AggregationUpdate.replaceWith(...) -> $replaceWith : { ... } AggregationUpdate update = Aggregation.newUpdate() .set("average").toValue(ArithmeticOperators.valueOf("tests").avg()) .set("grade").toValue(ConditionalOperators.switchCases( when(valueOf("average").greaterThanEqualToValue(90)).then("A"), when(valueOf("average").greaterThanEqualToValue(80)).then("B"), when(valueOf("average").greaterThanEqualToValue(70)).then("C"), when(valueOf("average").greaterThanEqualToValue(60)).then("D")) .defaultTo("F") ); template.update(Student.class) .apply(update) .all();
1 parent 0cc3532 commit 07b52d0

25 files changed

+2975
-152
lines changed

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

+15-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
import org.springframework.data.mongodb.core.query.Query;
2121
import org.springframework.data.mongodb.core.query.Update;
22+
import org.springframework.data.mongodb.core.query.UpdateDefinition;
2223
import org.springframework.lang.Nullable;
2324

2425
import com.mongodb.client.result.UpdateResult;
@@ -150,14 +151,27 @@ interface TerminatingUpdate<T> extends TerminatingFindAndModify<T>, FindAndModif
150151
*/
151152
interface UpdateWithUpdate<T> {
152153

154+
/**
155+
* Set the {@link UpdateDefinition} to be applied.
156+
*
157+
* @param update must not be {@literal null}.
158+
* @return new instance of {@link TerminatingUpdate}.
159+
* @throws IllegalArgumentException if update is {@literal null}.
160+
*/
161+
TerminatingUpdate<T> apply(UpdateDefinition update);
162+
153163
/**
154164
* Set the {@link Update} to be applied.
155165
*
156166
* @param update must not be {@literal null}.
157167
* @return new instance of {@link TerminatingUpdate}.
158168
* @throws IllegalArgumentException if update is {@literal null}.
169+
* @deprecated since 2.3 in favor of {@link #apply(UpdateDefinition)}.
159170
*/
160-
TerminatingUpdate<T> apply(Update update);
171+
@Deprecated
172+
default TerminatingUpdate<T> apply(Update update) {
173+
return apply((UpdateDefinition) update);
174+
}
161175

162176
/**
163177
* Specify {@code replacement} object.

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

+4-4
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
import lombok.experimental.FieldDefaults;
2222

2323
import org.springframework.data.mongodb.core.query.Query;
24-
import org.springframework.data.mongodb.core.query.Update;
24+
import org.springframework.data.mongodb.core.query.UpdateDefinition;
2525
import org.springframework.lang.Nullable;
2626
import org.springframework.util.Assert;
2727
import org.springframework.util.StringUtils;
@@ -67,7 +67,7 @@ static class ExecutableUpdateSupport<T>
6767
@NonNull MongoTemplate template;
6868
@NonNull Class domainType;
6969
Query query;
70-
@Nullable Update update;
70+
@Nullable UpdateDefinition update;
7171
@Nullable String collection;
7272
@Nullable FindAndModifyOptions findAndModifyOptions;
7373
@Nullable FindAndReplaceOptions findAndReplaceOptions;
@@ -76,10 +76,10 @@ static class ExecutableUpdateSupport<T>
7676

7777
/*
7878
* (non-Javadoc)
79-
* @see org.springframework.data.mongodb.core.ExecutableUpdateOperation.UpdateWithUpdate#apply(Update)
79+
* @see org.springframework.data.mongodb.core.ExecutableUpdateOperation.UpdateWithUpdate#apply(org.springframework.data.mongodb.core.query.UpdateDefinition)
8080
*/
8181
@Override
82-
public TerminatingUpdate<T> apply(Update update) {
82+
public TerminatingUpdate<T> apply(UpdateDefinition update) {
8383

8484
Assert.notNull(update, "Update must not be null!");
8585

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

+275-19
Large diffs are not rendered by default.

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

+77-35
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,9 @@
6868
import org.springframework.data.mongodb.core.aggregation.AggregationOperationContext;
6969
import org.springframework.data.mongodb.core.aggregation.AggregationOptions;
7070
import org.springframework.data.mongodb.core.aggregation.AggregationResults;
71+
import org.springframework.data.mongodb.core.aggregation.AggregationUpdate;
7172
import org.springframework.data.mongodb.core.aggregation.Fields;
73+
import org.springframework.data.mongodb.core.aggregation.RelaxedTypeBasedAggregationOperationContext;
7274
import org.springframework.data.mongodb.core.aggregation.TypeBasedAggregationOperationContext;
7375
import org.springframework.data.mongodb.core.aggregation.TypedAggregation;
7476
import org.springframework.data.mongodb.core.convert.DbRefResolver;
@@ -108,7 +110,6 @@
108110
import org.springframework.data.mongodb.core.query.Meta.CursorOption;
109111
import org.springframework.data.mongodb.core.query.NearQuery;
110112
import org.springframework.data.mongodb.core.query.Query;
111-
import org.springframework.data.mongodb.core.query.Update;
112113
import org.springframework.data.mongodb.core.query.UpdateDefinition;
113114
import org.springframework.data.mongodb.core.query.UpdateDefinition.ArrayFilter;
114115
import org.springframework.data.mongodb.core.validation.Validator;
@@ -1043,25 +1044,25 @@ public <T> GeoResults<T> geoNear(NearQuery near, Class<?> domainType, String col
10431044

10441045
@Nullable
10451046
@Override
1046-
public <T> T findAndModify(Query query, Update update, Class<T> entityClass) {
1047+
public <T> T findAndModify(Query query, UpdateDefinition update, Class<T> entityClass) {
10471048
return findAndModify(query, update, new FindAndModifyOptions(), entityClass, getCollectionName(entityClass));
10481049
}
10491050

10501051
@Nullable
10511052
@Override
1052-
public <T> T findAndModify(Query query, Update update, Class<T> entityClass, String collectionName) {
1053+
public <T> T findAndModify(Query query, UpdateDefinition update, Class<T> entityClass, String collectionName) {
10531054
return findAndModify(query, update, new FindAndModifyOptions(), entityClass, collectionName);
10541055
}
10551056

10561057
@Nullable
10571058
@Override
1058-
public <T> T findAndModify(Query query, Update update, FindAndModifyOptions options, Class<T> entityClass) {
1059+
public <T> T findAndModify(Query query, UpdateDefinition update, FindAndModifyOptions options, Class<T> entityClass) {
10591060
return findAndModify(query, update, options, entityClass, getCollectionName(entityClass));
10601061
}
10611062

10621063
@Nullable
10631064
@Override
1064-
public <T> T findAndModify(Query query, Update update, FindAndModifyOptions options, Class<T> entityClass,
1065+
public <T> T findAndModify(Query query, UpdateDefinition update, FindAndModifyOptions options, Class<T> entityClass,
10651066
String collectionName) {
10661067

10671068
Assert.notNull(query, "Query must not be null!");
@@ -1561,53 +1562,54 @@ public Object doInCollection(MongoCollection<Document> collection) throws MongoE
15611562
}
15621563

15631564
@Override
1564-
public UpdateResult upsert(Query query, Update update, Class<?> entityClass) {
1565+
public UpdateResult upsert(Query query, UpdateDefinition update, Class<?> entityClass) {
15651566
return doUpdate(getCollectionName(entityClass), query, update, entityClass, true, false);
15661567
}
15671568

15681569
@Override
1569-
public UpdateResult upsert(Query query, Update update, String collectionName) {
1570+
public UpdateResult upsert(Query query, UpdateDefinition update, String collectionName) {
15701571
return doUpdate(collectionName, query, update, null, true, false);
15711572
}
15721573

15731574
@Override
1574-
public UpdateResult upsert(Query query, Update update, Class<?> entityClass, String collectionName) {
1575+
public UpdateResult upsert(Query query, UpdateDefinition update, Class<?> entityClass, String collectionName) {
15751576

15761577
Assert.notNull(entityClass, "EntityClass must not be null!");
15771578

15781579
return doUpdate(collectionName, query, update, entityClass, true, false);
15791580
}
15801581

15811582
@Override
1582-
public UpdateResult updateFirst(Query query, Update update, Class<?> entityClass) {
1583+
public UpdateResult updateFirst(Query query, UpdateDefinition update, Class<?> entityClass) {
15831584
return doUpdate(getCollectionName(entityClass), query, update, entityClass, false, false);
15841585
}
15851586

15861587
@Override
1587-
public UpdateResult updateFirst(final Query query, final Update update, final String collectionName) {
1588+
public UpdateResult updateFirst(final Query query, final UpdateDefinition update, final String collectionName) {
15881589
return doUpdate(collectionName, query, update, null, false, false);
15891590
}
15901591

15911592
@Override
1592-
public UpdateResult updateFirst(Query query, Update update, Class<?> entityClass, String collectionName) {
1593+
public UpdateResult updateFirst(Query query, UpdateDefinition update, Class<?> entityClass, String collectionName) {
15931594

15941595
Assert.notNull(entityClass, "EntityClass must not be null!");
15951596

15961597
return doUpdate(collectionName, query, update, entityClass, false, false);
15971598
}
15981599

15991600
@Override
1600-
public UpdateResult updateMulti(Query query, Update update, Class<?> entityClass) {
1601+
public UpdateResult updateMulti(Query query, UpdateDefinition update, Class<?> entityClass) {
16011602
return doUpdate(getCollectionName(entityClass), query, update, entityClass, false, true);
16021603
}
16031604

16041605
@Override
1605-
public UpdateResult updateMulti(final Query query, final Update update, String collectionName) {
1606+
public UpdateResult updateMulti(final Query query, final UpdateDefinition update, String collectionName) {
16061607
return doUpdate(collectionName, query, update, null, false, true);
16071608
}
16081609

16091610
@Override
1610-
public UpdateResult updateMulti(final Query query, final Update update, Class<?> entityClass, String collectionName) {
1611+
public UpdateResult updateMulti(final Query query, final UpdateDefinition update, Class<?> entityClass,
1612+
String collectionName) {
16111613

16121614
Assert.notNull(entityClass, "EntityClass must not be null!");
16131615

@@ -1622,24 +1624,52 @@ protected UpdateResult doUpdate(final String collectionName, final Query query,
16221624
Assert.notNull(query, "Query must not be null!");
16231625
Assert.notNull(update, "Update must not be null!");
16241626

1625-
return execute(collectionName, collection -> {
1627+
MongoPersistentEntity<?> entity = entityClass == null ? null : getPersistentEntity(entityClass);
1628+
increaseVersionForUpdateIfNecessary(entity, update);
16261629

1627-
MongoPersistentEntity<?> entity = entityClass == null ? null : getPersistentEntity(entityClass);
1630+
UpdateOptions opts = new UpdateOptions();
1631+
opts.upsert(upsert);
16281632

1629-
increaseVersionForUpdateIfNecessary(entity, update);
1633+
if (update.hasArrayFilters()) {
1634+
opts.arrayFilters(update.getArrayFilters().stream().map(ArrayFilter::asDocument).collect(Collectors.toList()));
1635+
}
16301636

1631-
UpdateOptions opts = new UpdateOptions();
1632-
opts.upsert(upsert);
1637+
Document queryObj = new Document();
16331638

1634-
if (update.hasArrayFilters()) {
1635-
opts.arrayFilters(update.getArrayFilters().stream().map(ArrayFilter::asDocument).collect(Collectors.toList()));
1636-
}
1639+
if (query != null) {
1640+
queryObj.putAll(queryMapper.getMappedObject(query.getQueryObject(), entity));
1641+
}
16371642

1638-
Document queryObj = new Document();
1643+
if (multi && update.isIsolated() && !queryObj.containsKey("$isolated")) {
1644+
queryObj.put("$isolated", 1);
1645+
}
16391646

1640-
if (query != null) {
1641-
queryObj.putAll(queryMapper.getMappedObject(query.getQueryObject(), entity));
1642-
}
1647+
if (update instanceof AggregationUpdate) {
1648+
1649+
AggregationOperationContext context = entityClass != null
1650+
? new RelaxedTypeBasedAggregationOperationContext(entityClass, mappingContext, queryMapper)
1651+
: Aggregation.DEFAULT_CONTEXT;
1652+
1653+
AggregationUpdate aUppdate = ((AggregationUpdate) update);
1654+
List<Document> pipeline = new AggregationUtil(queryMapper, mappingContext).createPipeline(aUppdate, context);
1655+
1656+
return execute(collectionName, collection -> {
1657+
1658+
MongoAction mongoAction = new MongoAction(writeConcern, MongoActionOperation.UPDATE, collectionName,
1659+
entityClass, update.getUpdateObject(), queryObj);
1660+
WriteConcern writeConcernToUse = prepareWriteConcern(mongoAction);
1661+
1662+
collection = writeConcernToUse != null ? collection.withWriteConcern(writeConcernToUse) : collection;
1663+
1664+
if (multi) {
1665+
return collection.updateMany(queryObj, pipeline, opts);
1666+
}
1667+
1668+
return collection.updateOne(queryObj, pipeline, opts);
1669+
});
1670+
}
1671+
1672+
return execute(collectionName, collection -> {
16431673

16441674
operations.forType(entityClass) //
16451675
.getCollation(query) //
@@ -1649,10 +1679,6 @@ protected UpdateResult doUpdate(final String collectionName, final Query query,
16491679
Document updateObj = update instanceof MappedUpdate ? update.getUpdateObject()
16501680
: updateMapper.getMappedObject(update.getUpdateObject(), entity);
16511681

1652-
if (multi && update.isIsolated() && !queryObj.containsKey("$isolated")) {
1653-
queryObj.put("$isolated", 1);
1654-
}
1655-
16561682
if (LOGGER.isDebugEnabled()) {
16571683
LOGGER.debug("Calling update using query: {} and update: {} in collection: {}", serializeToJsonSafely(queryObj),
16581684
serializeToJsonSafely(updateObj), collectionName);
@@ -2640,7 +2666,7 @@ protected <T> T doFindAndRemove(String collectionName, Document query, Document
26402666

26412667
@SuppressWarnings("ConstantConditions")
26422668
protected <T> T doFindAndModify(String collectionName, Document query, Document fields, Document sort,
2643-
Class<T> entityClass, Update update, @Nullable FindAndModifyOptions options) {
2669+
Class<T> entityClass, UpdateDefinition update, @Nullable FindAndModifyOptions options) {
26442670

26452671
EntityReader<? super T, Bson> readerToUse = this.mongoConverter;
26462672

@@ -2653,7 +2679,18 @@ protected <T> T doFindAndModify(String collectionName, Document query, Document
26532679
increaseVersionForUpdateIfNecessary(entity, update);
26542680

26552681
Document mappedQuery = queryMapper.getMappedObject(query, entity);
2656-
Document mappedUpdate = updateMapper.getMappedObject(update.getUpdateObject(), entity);
2682+
2683+
Object mappedUpdate = new Document();
2684+
if (update instanceof AggregationUpdate) {
2685+
2686+
AggregationOperationContext context = entityClass != null
2687+
? new RelaxedTypeBasedAggregationOperationContext(entityClass, mappingContext, queryMapper)
2688+
: Aggregation.DEFAULT_CONTEXT;
2689+
2690+
mappedUpdate = new AggregationUtil(queryMapper, mappingContext).createPipeline((Aggregation) update, context);
2691+
} else {
2692+
mappedUpdate = updateMapper.getMappedObject(update.getUpdateObject(), entity);
2693+
}
26572694

26582695
if (LOGGER.isDebugEnabled()) {
26592696
LOGGER.debug(
@@ -3027,11 +3064,11 @@ private static class FindAndModifyCallback implements CollectionCallback<Documen
30273064
private final Document query;
30283065
private final Document fields;
30293066
private final Document sort;
3030-
private final Document update;
3067+
private final Object update;
30313068
private final List<Document> arrayFilters;
30323069
private final FindAndModifyOptions options;
30333070

3034-
public FindAndModifyCallback(Document query, Document fields, Document sort, Document update,
3071+
public FindAndModifyCallback(Document query, Document fields, Document sort, Object update,
30353072
List<Document> arrayFilters, FindAndModifyOptions options) {
30363073
this.query = query;
30373074
this.fields = fields;
@@ -3059,7 +3096,12 @@ public Document doInCollection(MongoCollection<Document> collection) throws Mong
30593096
opts.arrayFilters(arrayFilters);
30603097
}
30613098

3062-
return collection.findOneAndUpdate(query, update, opts);
3099+
if (update instanceof Document) {
3100+
return collection.findOneAndUpdate(query, (Document) update, opts);
3101+
} else if (update instanceof List) {
3102+
return collection.findOneAndUpdate(query, (List<Document>) update, opts);
3103+
}
3104+
throw new IllegalArgumentException("doh - that does not work");
30633105
}
30643106
}
30653107

0 commit comments

Comments
 (0)