Skip to content

Commit 67bd722

Browse files
committed
Polishing.
Extract common code into BulkOperationsSupport. Reorder methods. Add missing verifyComplete to tests. See #2821 Original pull request: #4342
1 parent 86dd81f commit 67bd722

File tree

8 files changed

+425
-504
lines changed

8 files changed

+425
-504
lines changed

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,9 @@
3131
* {@link #execute()}.
3232
*
3333
* <pre class="code">
34-
* MongoTemplate template = …;
34+
* MongoOperations ops = …;
3535
*
36-
* template.bulkOps(BulkMode.UNORDERED, Person.class)
36+
* ops.bulkOps(BulkMode.UNORDERED, Person.class)
3737
* .insert(newPerson)
3838
* .updateOne(where("firstname").is("Joe"), Update.update("lastname", "Doe"))
3939
* .execute();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
/*
2+
* Copyright 2023 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.mongodb.core;
17+
18+
import java.util.ArrayList;
19+
import java.util.List;
20+
import java.util.Optional;
21+
22+
import org.bson.Document;
23+
import org.bson.conversions.Bson;
24+
import org.springframework.context.ApplicationEvent;
25+
import org.springframework.data.mapping.PersistentEntity;
26+
import org.springframework.data.mongodb.core.BulkOperations.BulkMode;
27+
import org.springframework.data.mongodb.core.aggregation.AggregationOperationContext;
28+
import org.springframework.data.mongodb.core.aggregation.AggregationUpdate;
29+
import org.springframework.data.mongodb.core.aggregation.RelaxedTypeBasedAggregationOperationContext;
30+
import org.springframework.data.mongodb.core.convert.QueryMapper;
31+
import org.springframework.data.mongodb.core.convert.UpdateMapper;
32+
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
33+
import org.springframework.data.mongodb.core.mapping.event.AfterSaveEvent;
34+
import org.springframework.data.mongodb.core.mapping.event.BeforeSaveEvent;
35+
import org.springframework.data.mongodb.core.query.Collation;
36+
import org.springframework.data.mongodb.core.query.Query;
37+
import org.springframework.data.mongodb.core.query.Update;
38+
import org.springframework.data.mongodb.core.query.UpdateDefinition;
39+
import org.springframework.data.mongodb.core.query.UpdateDefinition.ArrayFilter;
40+
import org.springframework.util.Assert;
41+
42+
import com.mongodb.client.model.BulkWriteOptions;
43+
import com.mongodb.client.model.DeleteManyModel;
44+
import com.mongodb.client.model.DeleteOneModel;
45+
import com.mongodb.client.model.InsertOneModel;
46+
import com.mongodb.client.model.ReplaceOneModel;
47+
import com.mongodb.client.model.UpdateManyModel;
48+
import com.mongodb.client.model.UpdateOneModel;
49+
import com.mongodb.client.model.UpdateOptions;
50+
import com.mongodb.client.model.WriteModel;
51+
52+
/**
53+
* Support class for bulk operations.
54+
*
55+
* @author Mark Paluch
56+
* @since 4.1
57+
*/
58+
abstract class BulkOperationsSupport {
59+
60+
private final String collectionName;
61+
62+
BulkOperationsSupport(String collectionName) {
63+
64+
Assert.hasText(collectionName, "CollectionName must not be null nor empty");
65+
66+
this.collectionName = collectionName;
67+
}
68+
69+
/**
70+
* Emit a {@link BeforeSaveEvent}.
71+
*
72+
* @param holder
73+
*/
74+
void maybeEmitBeforeSaveEvent(SourceAwareWriteModelHolder holder) {
75+
76+
if (holder.model() instanceof InsertOneModel) {
77+
78+
Document target = ((InsertOneModel<Document>) holder.model()).getDocument();
79+
maybeEmitEvent(new BeforeSaveEvent<>(holder.source(), target, collectionName));
80+
} else if (holder.model() instanceof ReplaceOneModel) {
81+
82+
Document target = ((ReplaceOneModel<Document>) holder.model()).getReplacement();
83+
maybeEmitEvent(new BeforeSaveEvent<>(holder.source(), target, collectionName));
84+
}
85+
}
86+
87+
/**
88+
* Emit a {@link AfterSaveEvent}.
89+
*
90+
* @param holder
91+
*/
92+
void maybeEmitAfterSaveEvent(SourceAwareWriteModelHolder holder) {
93+
94+
if (holder.model() instanceof InsertOneModel) {
95+
96+
Document target = ((InsertOneModel<Document>) holder.model()).getDocument();
97+
maybeEmitEvent(new AfterSaveEvent<>(holder.source(), target, collectionName));
98+
} else if (holder.model() instanceof ReplaceOneModel) {
99+
100+
Document target = ((ReplaceOneModel<Document>) holder.model()).getReplacement();
101+
maybeEmitEvent(new AfterSaveEvent<>(holder.source(), target, collectionName));
102+
}
103+
}
104+
105+
WriteModel<Document> mapWriteModel(Object source, WriteModel<Document> writeModel) {
106+
107+
if (writeModel instanceof UpdateOneModel<Document> model) {
108+
109+
if (source instanceof AggregationUpdate aggregationUpdate) {
110+
111+
List<Document> pipeline = mapUpdatePipeline(aggregationUpdate);
112+
return new UpdateOneModel<>(getMappedQuery(model.getFilter()), pipeline, model.getOptions());
113+
}
114+
115+
return new UpdateOneModel<>(getMappedQuery(model.getFilter()), getMappedUpdate(model.getUpdate()),
116+
model.getOptions());
117+
}
118+
119+
if (writeModel instanceof UpdateManyModel<Document> model) {
120+
121+
if (source instanceof AggregationUpdate aggregationUpdate) {
122+
123+
List<Document> pipeline = mapUpdatePipeline(aggregationUpdate);
124+
return new UpdateManyModel<>(getMappedQuery(model.getFilter()), pipeline, model.getOptions());
125+
}
126+
127+
return new UpdateManyModel<>(getMappedQuery(model.getFilter()), getMappedUpdate(model.getUpdate()),
128+
model.getOptions());
129+
}
130+
131+
if (writeModel instanceof DeleteOneModel<Document> model) {
132+
return new DeleteOneModel<>(getMappedQuery(model.getFilter()), model.getOptions());
133+
}
134+
135+
if (writeModel instanceof DeleteManyModel<Document> model) {
136+
return new DeleteManyModel<>(getMappedQuery(model.getFilter()), model.getOptions());
137+
}
138+
139+
return writeModel;
140+
}
141+
142+
private List<Document> mapUpdatePipeline(AggregationUpdate source) {
143+
144+
Class<?> type = entity().isPresent() ? entity().map(PersistentEntity::getType).get() : Object.class;
145+
AggregationOperationContext context = new RelaxedTypeBasedAggregationOperationContext(type,
146+
updateMapper().getMappingContext(), queryMapper());
147+
148+
return new AggregationUtil(queryMapper(), queryMapper().getMappingContext()).createPipeline(source, context);
149+
}
150+
151+
/**
152+
* Emit a {@link ApplicationEvent} if event multicasting is enabled.
153+
*
154+
* @param event
155+
*/
156+
protected abstract void maybeEmitEvent(ApplicationEvent event);
157+
158+
/**
159+
* @return the {@link UpdateMapper} to use.
160+
*/
161+
protected abstract UpdateMapper updateMapper();
162+
163+
/**
164+
* @return the {@link QueryMapper} to use.
165+
*/
166+
protected abstract QueryMapper queryMapper();
167+
168+
/**
169+
* @return the associated {@link PersistentEntity}. Can be {@link Optional#empty()}.
170+
*/
171+
protected abstract Optional<? extends MongoPersistentEntity<?>> entity();
172+
173+
protected Bson getMappedUpdate(Bson update) {
174+
return updateMapper().getMappedObject(update, entity());
175+
}
176+
177+
protected Bson getMappedQuery(Bson query) {
178+
return queryMapper().getMappedObject(query, entity());
179+
}
180+
181+
protected static BulkWriteOptions getBulkWriteOptions(BulkMode bulkMode) {
182+
183+
BulkWriteOptions options = new BulkWriteOptions();
184+
185+
return switch (bulkMode) {
186+
case ORDERED -> options.ordered(true);
187+
case UNORDERED -> options.ordered(false);
188+
};
189+
}
190+
191+
/**
192+
* @param filterQuery The {@link Query} to read a potential {@link Collation} from. Must not be {@literal null}.
193+
* @param update The {@link Update} to apply
194+
* @param upsert flag to indicate if document should be upserted.
195+
* @return new instance of {@link UpdateOptions}.
196+
*/
197+
protected static UpdateOptions computeUpdateOptions(Query filterQuery, UpdateDefinition update, boolean upsert) {
198+
199+
UpdateOptions options = new UpdateOptions();
200+
options.upsert(upsert);
201+
202+
if (update.hasArrayFilters()) {
203+
List<Document> list = new ArrayList<>(update.getArrayFilters().size());
204+
for (ArrayFilter arrayFilter : update.getArrayFilters()) {
205+
list.add(arrayFilter.asDocument());
206+
}
207+
options.arrayFilters(list);
208+
}
209+
210+
filterQuery.getCollation().map(Collation::toMongoCollation).ifPresent(options::collation);
211+
return options;
212+
}
213+
214+
/**
215+
* Value object chaining together an actual source with its {@link WriteModel} representation.
216+
*
217+
* @author Christoph Strobl
218+
*/
219+
record SourceAwareWriteModelHolder(Object source, WriteModel<Document> model) {
220+
}
221+
}

0 commit comments

Comments
 (0)