Skip to content

Commit d79031b

Browse files
Apply sort to replace and bulk operation updates
Allow using sort parameter from the query for template replace as well as bulk update & replace operations. We now also mapped fields used in sort to the domain type considering field annotations. Also updated javadoc and reference documentation. Original Pull Request: #4888
1 parent 019d915 commit d79031b

17 files changed

+343
-152
lines changed

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

+9-4
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import java.util.List;
1919

20+
import org.springframework.data.domain.Sort;
2021
import org.springframework.data.mongodb.core.query.Query;
2122
import org.springframework.data.mongodb.core.query.Update;
2223
import org.springframework.data.mongodb.core.query.UpdateDefinition;
@@ -81,7 +82,8 @@ enum BulkMode {
8182
/**
8283
* Add a single update to the bulk operation. For the update request, only the first matching document is updated.
8384
*
84-
* @param query update criteria, must not be {@literal null}.
85+
* @param query update criteria, must not be {@literal null}. The {@link Query} may define a {@link Query#with(Sort)
86+
* sort order} to influence which document to update when potentially matching multiple candidates.
8587
* @param update {@link Update} operation to perform, must not be {@literal null}.
8688
* @return the current {@link BulkOperations} instance with the update added, will never be {@literal null}.
8789
*/
@@ -92,7 +94,8 @@ default BulkOperations updateOne(Query query, Update update) {
9294
/**
9395
* Add a single update to the bulk operation. For the update request, only the first matching document is updated.
9496
*
95-
* @param query update criteria, must not be {@literal null}.
97+
* @param query update criteria, must not be {@literal null}. The {@link Query} may define a {@link Query#with(Sort)
98+
* sort order} to influence which document to update when potentially matching multiple candidates.
9699
* @param update {@link Update} operation to perform, must not be {@literal null}.
97100
* @return the current {@link BulkOperations} instance with the update added, will never be {@literal null}.
98101
* @since 4.1
@@ -187,7 +190,8 @@ default BulkOperations upsert(Query query, Update update) {
187190
/**
188191
* Add a single replace operation to the bulk operation.
189192
*
190-
* @param query Update criteria.
193+
* @param query Replace criteria. The {@link Query} may define a {@link Query#with(Sort) sort order} to influence
194+
* which document to replace when potentially matching multiple candidates.
191195
* @param replacement the replacement document. Must not be {@literal null}.
192196
* @return the current {@link BulkOperations} instance with the replacement added, will never be {@literal null}.
193197
* @since 2.2
@@ -199,7 +203,8 @@ default BulkOperations replaceOne(Query query, Object replacement) {
199203
/**
200204
* Add a single replace operation to the bulk operation.
201205
*
202-
* @param query Update criteria.
206+
* @param query Replace criteria. The {@link Query} may define a {@link Query#with(Sort) sort order} to influence
207+
* which document to replace when potentially matching multiple candidates.
203208
* @param replacement the replacement document. Must not be {@literal null}.
204209
* @param options the {@link FindAndModifyOptions} holding additional information. Must not be {@literal null}.
205210
* @return the current {@link BulkOperations} instance with the replacement added, will never be {@literal null}.

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

+23-1
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,11 @@ WriteModel<Document> mapWriteModel(Object source, WriteModel<Document> writeMode
106106

107107
if (writeModel instanceof UpdateOneModel<Document> model) {
108108

109+
Bson sort = model.getOptions().getSort();
110+
if (sort instanceof Document sortDocument) {
111+
model.getOptions().sort(updateMapper().getMappedSort(sortDocument, entity().orElse(null)));
112+
}
113+
109114
if (source instanceof AggregationUpdate aggregationUpdate) {
110115

111116
List<Document> pipeline = mapUpdatePipeline(aggregationUpdate);
@@ -136,6 +141,17 @@ WriteModel<Document> mapWriteModel(Object source, WriteModel<Document> writeMode
136141
return new DeleteManyModel<>(getMappedQuery(model.getFilter()), model.getOptions());
137142
}
138143

144+
if (writeModel instanceof ReplaceOneModel<Document> model) {
145+
146+
Bson sort = model.getReplaceOptions().getSort();
147+
148+
if (sort instanceof Document sortDocument) {
149+
model.getReplaceOptions().sort(updateMapper().getMappedSort(sortDocument, entity().orElse(null)));
150+
}
151+
return new ReplaceOneModel<>(getMappedQuery(model.getFilter()), model.getReplacement(),
152+
model.getReplaceOptions());
153+
}
154+
139155
return writeModel;
140156
}
141157

@@ -192,9 +208,11 @@ protected static BulkWriteOptions getBulkWriteOptions(BulkMode bulkMode) {
192208
* @param filterQuery The {@link Query} to read a potential {@link Collation} from. Must not be {@literal null}.
193209
* @param update The {@link Update} to apply
194210
* @param upsert flag to indicate if document should be upserted.
211+
* @param multi flag to indicate if update might affect multiple documents.
195212
* @return new instance of {@link UpdateOptions}.
196213
*/
197-
protected static UpdateOptions computeUpdateOptions(Query filterQuery, UpdateDefinition update, boolean upsert) {
214+
protected UpdateOptions computeUpdateOptions(Query filterQuery, UpdateDefinition update, boolean upsert,
215+
boolean multi) {
198216

199217
UpdateOptions options = new UpdateOptions();
200218
options.upsert(upsert);
@@ -207,6 +225,10 @@ protected static UpdateOptions computeUpdateOptions(Query filterQuery, UpdateDef
207225
options.arrayFilters(list);
208226
}
209227

228+
if (!multi && filterQuery.isSorted()) {
229+
options.sort(filterQuery.getSortObject());
230+
}
231+
210232
filterQuery.getCollation().map(Collation::toMongoCollation).ifPresent(options::collation);
211233
return options;
212234
}

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

+5-3
Original file line numberDiff line numberDiff line change
@@ -229,12 +229,14 @@ public BulkOperations replaceOne(Query query, Object replacement, FindAndReplace
229229

230230
ReplaceOptions replaceOptions = new ReplaceOptions();
231231
replaceOptions.upsert(options.isUpsert());
232+
if (query.isSorted()) {
233+
replaceOptions.sort(query.getSortObject());
234+
}
232235
query.getCollation().map(Collation::toMongoCollation).ifPresent(replaceOptions::collation);
233236

234237
maybeEmitEvent(new BeforeConvertEvent<>(replacement, collectionName));
235238
Object source = maybeInvokeBeforeConvertCallback(replacement);
236-
addModel(source,
237-
new ReplaceOneModel<>(getMappedQuery(query.getQueryObject()), getMappedObject(source), replaceOptions));
239+
addModel(source, new ReplaceOneModel<>(query.getQueryObject(), getMappedObject(source), replaceOptions));
238240

239241
return this;
240242
}
@@ -315,7 +317,7 @@ private BulkOperations update(Query query, UpdateDefinition update, boolean upse
315317
Assert.notNull(query, "Query must not be null");
316318
Assert.notNull(update, "Update must not be null");
317319

318-
UpdateOptions options = computeUpdateOptions(query, update, upsert);
320+
UpdateOptions options = computeUpdateOptions(query, update, upsert, multi);
319321

320322
if (multi) {
321323
addModel(update, new UpdateManyModel<>(query.getQueryObject(), update.getUpdateObject(), options));

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

+7-4
Original file line numberDiff line numberDiff line change
@@ -189,13 +189,16 @@ public ReactiveBulkOperations replaceOne(Query query, Object replacement, FindAn
189189

190190
ReplaceOptions replaceOptions = new ReplaceOptions();
191191
replaceOptions.upsert(options.isUpsert());
192+
if (query.isSorted()) {
193+
replaceOptions.sort(query.getSortObject());
194+
}
192195
query.getCollation().map(Collation::toMongoCollation).ifPresent(replaceOptions::collation);
193196

194197
this.models.add(Mono.just(replacement).flatMap(it -> {
195198
maybeEmitEvent(new BeforeConvertEvent<>(it, collectionName));
196199
return maybeInvokeBeforeConvertCallback(it);
197200
}).map(it -> new SourceAwareWriteModelHolder(it,
198-
new ReplaceOneModel<>(getMappedQuery(query.getQueryObject()), getMappedObject(it), replaceOptions))));
201+
new ReplaceOneModel<>(query.getQueryObject(), getMappedObject(it), replaceOptions))));
199202

200203
return this;
201204
}
@@ -218,13 +221,13 @@ private Mono<BulkWriteResult> bulkWriteTo(MongoCollection<Document> collection)
218221

219222
Flux<SourceAwareWriteModelHolder> concat = Flux.concat(models).flatMapSequential(it -> {
220223

221-
if (it.model()instanceof InsertOneModel<Document> iom) {
224+
if (it.model() instanceof InsertOneModel<Document> iom) {
222225

223226
Document target = iom.getDocument();
224227
maybeEmitBeforeSaveEvent(it);
225228
return maybeInvokeBeforeSaveCallback(it.source(), target)
226229
.map(afterCallback -> new SourceAwareWriteModelHolder(afterCallback, mapWriteModel(afterCallback, iom)));
227-
} else if (it.model()instanceof ReplaceOneModel<Document> rom) {
230+
} else if (it.model() instanceof ReplaceOneModel<Document> rom) {
228231

229232
Document target = rom.getReplacement();
230233
maybeEmitBeforeSaveEvent(it);
@@ -265,7 +268,7 @@ private ReactiveBulkOperations update(Query query, UpdateDefinition update, bool
265268
Assert.notNull(query, "Query must not be null");
266269
Assert.notNull(update, "Update must not be null");
267270

268-
UpdateOptions options = computeUpdateOptions(query, update, upsert);
271+
UpdateOptions options = computeUpdateOptions(query, update, upsert, multi);
269272

270273
this.models.add(Mono.just(update).map(it -> {
271274
if (multi) {

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

+19-13
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
import org.bson.Document;
2727
import org.springframework.data.domain.KeysetScrollPosition;
28+
import org.springframework.data.domain.Sort;
2829
import org.springframework.data.domain.Window;
2930
import org.springframework.data.geo.GeoResults;
3031
import org.springframework.data.mongodb.core.BulkOperations.BulkMode;
@@ -1604,8 +1605,9 @@ default long exactCount(Query query, String collectionName) {
16041605
* A potential {@link org.springframework.data.annotation.Version} property of the {@literal entityClass} will be
16051606
* auto-incremented if not explicitly specified in the update.
16061607
*
1607-
* @param query the query document that specifies the criteria used to select a document to be updated. Must not be
1608-
* {@literal null}.
1608+
* @param query the query document that specifies the criteria used to select a document to be updated. The
1609+
* {@link Query} may define a {@link Query#with(Sort) sort order} to influence which document to update when
1610+
* potentially matching multiple candidates. Must not be {@literal null}.
16091611
* @param update the {@link UpdateDefinition} that contains the updated object or {@code $} operators to manipulate
16101612
* the existing. Must not be {@literal null}.
16111613
* @param entityClass class that determines the collection to use.
@@ -1623,12 +1625,11 @@ default long exactCount(Query query, String collectionName) {
16231625
* the provided updated document. <br />
16241626
* <strong>NOTE:</strong> Any additional support for field mapping, versions, etc. is not available due to the lack of
16251627
* domain type information. Use {@link #updateFirst(Query, UpdateDefinition, Class, String)} to get full type specific
1626-
* support. <br />
1627-
* <strong>NOTE:</strong> {@link Query#getSortObject() sorting} is not supported by {@code db.collection.updateOne}.
1628-
* Use {@link #findAndModify(Query, UpdateDefinition, Class, String)} instead.
1628+
* support.
16291629
*
1630-
* @param query the query document that specifies the criteria used to select a document to be updated. Must not be
1631-
* {@literal null}.
1630+
* @param query the query document that specifies the criteria used to select a document to be updated. The
1631+
* {@link Query} may define a {@link Query#with(Sort) sort order} to influence which document to update when
1632+
* potentially matching multiple candidates. Must not be {@literal null}.
16321633
* @param update the {@link UpdateDefinition} that contains the updated object or {@code $} operators to manipulate
16331634
* the existing. Must not be {@literal null}.
16341635
* @param collectionName name of the collection to update the object in. Must not be {@literal null}.
@@ -1646,8 +1647,9 @@ default long exactCount(Query query, String collectionName) {
16461647
* A potential {@link org.springframework.data.annotation.Version} property of the {@literal entityClass} will be auto
16471648
* incremented if not explicitly specified in the update.
16481649
*
1649-
* @param query the query document that specifies the criteria used to select a document to be updated. Must not be
1650-
* {@literal null}.
1650+
* @param query the query document that specifies the criteria used to select a document to be updated. The
1651+
* {@link Query} may define a {@link Query#with(Sort) sort order} to influence which document to update when
1652+
* potentially matching multiple candidates. Must not be {@literal null}.
16511653
* @param update the {@link UpdateDefinition} that contains the updated object or {@code $} operators to manipulate
16521654
* the existing. Must not be {@literal null}.
16531655
* @param entityClass class of the pojo to be operated on. Must not be {@literal null}.
@@ -1833,7 +1835,8 @@ default long exactCount(Query query, String collectionName) {
18331835
*
18341836
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a document. The query may
18351837
* contain an index {@link Query#withHint(String) hint} or the {@link Query#collation(Collation) collation}
1836-
* to use. Must not be {@literal null}.
1838+
* to use. The {@link Query} may define a {@link Query#with(Sort) sort order} to influence which document to
1839+
* replace when potentially matching multiple candidates. Must not be {@literal null}.
18371840
* @param replacement the replacement document. Must not be {@literal null}.
18381841
* @return the {@link UpdateResult} which lets you access the results of the previous replacement.
18391842
* @throws org.springframework.data.mapping.MappingException if the collection name cannot be
@@ -1850,7 +1853,8 @@ default <T> UpdateResult replace(Query query, T replacement) {
18501853
*
18511854
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a document. The query may
18521855
* contain an index {@link Query#withHint(String) hint} or the {@link Query#collation(Collation) collation}
1853-
* to use. Must not be {@literal null}.
1856+
* to use. The {@link Query} may define a {@link Query#with(Sort) sort order} to influence which document to
1857+
* replace when potentially matching multiple candidates. Must not be {@literal null}.
18541858
* @param replacement the replacement document. Must not be {@literal null}.
18551859
* @param collectionName the collection to query. Must not be {@literal null}.
18561860
* @return the {@link UpdateResult} which lets you access the results of the previous replacement.
@@ -1866,7 +1870,8 @@ default <T> UpdateResult replace(Query query, T replacement, String collectionNa
18661870
*
18671871
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a document.The query may
18681872
* contain an index {@link Query#withHint(String) hint} or the {@link Query#collation(Collation) collation}
1869-
* to use. Must not be {@literal null}.
1873+
* to use. The {@link Query} may define a {@link Query#with(Sort) sort order} to influence which document to
1874+
* replace when potentially matching multiple candidates. Must not be {@literal null}.
18701875
* @param replacement the replacement document. Must not be {@literal null}.
18711876
* @param options the {@link ReplaceOptions} holding additional information. Must not be {@literal null}.
18721877
* @return the {@link UpdateResult} which lets you access the results of the previous replacement.
@@ -1884,7 +1889,8 @@ default <T> UpdateResult replace(Query query, T replacement, ReplaceOptions opti
18841889
*
18851890
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a document. The query may *
18861891
* contain an index {@link Query#withHint(String) hint} or the {@link Query#collation(Collation) collation}
1887-
* to use. Must not be {@literal null}.
1892+
* to use. The {@link Query} may define a {@link Query#with(Sort) sort order} to influence which document to
1893+
* replace when potentially matching multiple candidates. Must not be {@literal null}.
18881894
* @param replacement the replacement document. Must not be {@literal null}.
18891895
* @param options the {@link ReplaceOptions} holding additional information. Must not be {@literal null}.
18901896
* @return the {@link UpdateResult} which lets you access the results of the previous replacement.

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

+4-1
Original file line numberDiff line numberDiff line change
@@ -765,7 +765,7 @@ UpdateOptions getUpdateOptions(@Nullable Class<?> domainType, @Nullable Query qu
765765
}
766766

767767
if (query != null && query.isSorted()) {
768-
options.sort(query.getSortObject());
768+
options.sort(getMappedSort(domainType != null ? mappingContext.getPersistentEntity(domainType) : null));
769769
}
770770

771771
HintFunction.from(getQuery().getHint()).ifPresent(codecRegistryProvider, options::hintString, options::hint);
@@ -799,6 +799,9 @@ ReplaceOptions getReplaceOptions(@Nullable Class<?> domainType, @Nullable Consum
799799
options.collation(updateOptions.getCollation());
800800
options.upsert(updateOptions.isUpsert());
801801
applyHint(options::hintString, options::hint);
802+
if (!isMulti() && getQuery().isSorted()) {
803+
options.sort(getMappedSort(domainType != null ? mappingContext.getPersistentEntity(domainType) : null));
804+
}
802805

803806
if (callback != null) {
804807
callback.accept(options);

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

+10-4
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
import java.util.List;
2121

22+
import org.springframework.data.domain.Sort;
2223
import org.springframework.data.mongodb.core.query.Query;
2324
import org.springframework.data.mongodb.core.query.UpdateDefinition;
2425

@@ -67,7 +68,8 @@ public interface ReactiveBulkOperations {
6768
/**
6869
* Add a single update to the bulk operation. For the update request, only the first matching document is updated.
6970
*
70-
* @param query update criteria, must not be {@literal null}.
71+
* @param query update criteria, must not be {@literal null}. The {@link Query} may define a {@link Query#with(Sort)
72+
* sort order} to influence which document to update when potentially matching multiple candidates.
7173
* @param update {@link UpdateDefinition} operation to perform, must not be {@literal null}.
7274
* @return the current {@link ReactiveBulkOperations} instance with the update added, will never be {@literal null}.
7375
*/
@@ -111,8 +113,11 @@ public interface ReactiveBulkOperations {
111113
/**
112114
* Add a single replace operation to the bulk operation.
113115
*
114-
* @param query Update criteria.
115-
* @param replacement the replacement document. Must not be {@literal null}.
116+
* @param query Replace criteria. The {@link Query} may define a {@link Query#with(Sort) sort order} to influence
117+
* which document to replace when potentially matching multiple candidates.
118+
* @param replacement the replacement document. Must not be {@literal null}. The {@link Query} may define a
119+
* {@link Query#with(Sort) sort order} to influence which document to replace when potentially matching
120+
* multiple candidates.
116121
* @return the current {@link ReactiveBulkOperations} instance with the replace added, will never be {@literal null}.
117122
*/
118123
default ReactiveBulkOperations replaceOne(Query query, Object replacement) {
@@ -122,7 +127,8 @@ default ReactiveBulkOperations replaceOne(Query query, Object replacement) {
122127
/**
123128
* Add a single replace operation to the bulk operation.
124129
*
125-
* @param query Update criteria.
130+
* @param query Replace criteria. The {@link Query} may define a {@link Query#with(Sort) sort order} to influence
131+
* which document to replace when potentially matching multiple candidates.
126132
* @param replacement the replacement document. Must not be {@literal null}.
127133
* @param options the {@link FindAndModifyOptions} holding additional information. Must not be {@literal null}.
128134
* @return the current {@link ReactiveBulkOperations} instance with the replace added, will never be {@literal null}.

0 commit comments

Comments
 (0)