Browse Source

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
pull/4915/head
Christoph Strobl 10 months ago
parent
commit
d79031b60d
No known key found for this signature in database
GPG Key ID: E6054036D0C37A4B
  1. 13
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/BulkOperations.java
  2. 24
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/BulkOperationsSupport.java
  3. 8
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultBulkOperations.java
  4. 7
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultReactiveBulkOperations.java
  5. 32
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoOperations.java
  6. 5
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/QueryOperations.java
  7. 14
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveBulkOperations.java
  8. 36
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoOperations.java
  9. 51
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultBulkOperationsIntegrationTests.java
  10. 49
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultReactiveBulkOperationsTests.java
  11. 15
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateReplaceTests.java
  12. 58
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateTests.java
  13. 54
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateUpdateTests.java
  14. 22
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveMongoTemplateReplaceTests.java
  15. 36
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveMongoTemplateTests.java
  16. 57
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveMongoTemplateUpdateTests.java
  17. 2
      src/main/antora/modules/ROOT/pages/mongodb/template-crud-operations.adoc

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

@ -17,6 +17,7 @@ package org.springframework.data.mongodb.core; @@ -17,6 +17,7 @@ package org.springframework.data.mongodb.core;
import java.util.List;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.data.mongodb.core.query.UpdateDefinition;
@ -81,7 +82,8 @@ public interface BulkOperations { @@ -81,7 +82,8 @@ public interface BulkOperations {
/**
* Add a single update to the bulk operation. For the update request, only the first matching document is updated.
*
* @param query update criteria, must not be {@literal null}.
* @param query update criteria, must not be {@literal null}. The {@link Query} may define a {@link Query#with(Sort)
* sort order} to influence which document to update when potentially matching multiple candidates.
* @param update {@link Update} operation to perform, must not be {@literal null}.
* @return the current {@link BulkOperations} instance with the update added, will never be {@literal null}.
*/
@ -92,7 +94,8 @@ public interface BulkOperations { @@ -92,7 +94,8 @@ public interface BulkOperations {
/**
* Add a single update to the bulk operation. For the update request, only the first matching document is updated.
*
* @param query update criteria, must not be {@literal null}.
* @param query update criteria, must not be {@literal null}. The {@link Query} may define a {@link Query#with(Sort)
* sort order} to influence which document to update when potentially matching multiple candidates.
* @param update {@link Update} operation to perform, must not be {@literal null}.
* @return the current {@link BulkOperations} instance with the update added, will never be {@literal null}.
* @since 4.1
@ -187,7 +190,8 @@ public interface BulkOperations { @@ -187,7 +190,8 @@ public interface BulkOperations {
/**
* Add a single replace operation to the bulk operation.
*
* @param query Update criteria.
* @param query Replace criteria. The {@link Query} may define a {@link Query#with(Sort) sort order} to influence
* which document to replace when potentially matching multiple candidates.
* @param replacement the replacement document. Must not be {@literal null}.
* @return the current {@link BulkOperations} instance with the replacement added, will never be {@literal null}.
* @since 2.2
@ -199,7 +203,8 @@ public interface BulkOperations { @@ -199,7 +203,8 @@ public interface BulkOperations {
/**
* Add a single replace operation to the bulk operation.
*
* @param query Update criteria.
* @param query Replace criteria. The {@link Query} may define a {@link Query#with(Sort) sort order} to influence
* which document to replace when potentially matching multiple candidates.
* @param replacement the replacement document. Must not be {@literal null}.
* @param options the {@link FindAndModifyOptions} holding additional information. Must not be {@literal null}.
* @return the current {@link BulkOperations} instance with the replacement added, will never be {@literal null}.

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

@ -106,6 +106,11 @@ abstract class BulkOperationsSupport { @@ -106,6 +106,11 @@ abstract class BulkOperationsSupport {
if (writeModel instanceof UpdateOneModel<Document> model) {
Bson sort = model.getOptions().getSort();
if (sort instanceof Document sortDocument) {
model.getOptions().sort(updateMapper().getMappedSort(sortDocument, entity().orElse(null)));
}
if (source instanceof AggregationUpdate aggregationUpdate) {
List<Document> pipeline = mapUpdatePipeline(aggregationUpdate);
@ -136,6 +141,17 @@ abstract class BulkOperationsSupport { @@ -136,6 +141,17 @@ abstract class BulkOperationsSupport {
return new DeleteManyModel<>(getMappedQuery(model.getFilter()), model.getOptions());
}
if (writeModel instanceof ReplaceOneModel<Document> model) {
Bson sort = model.getReplaceOptions().getSort();
if (sort instanceof Document sortDocument) {
model.getReplaceOptions().sort(updateMapper().getMappedSort(sortDocument, entity().orElse(null)));
}
return new ReplaceOneModel<>(getMappedQuery(model.getFilter()), model.getReplacement(),
model.getReplaceOptions());
}
return writeModel;
}
@ -192,9 +208,11 @@ abstract class BulkOperationsSupport { @@ -192,9 +208,11 @@ abstract class BulkOperationsSupport {
* @param filterQuery The {@link Query} to read a potential {@link Collation} from. Must not be {@literal null}.
* @param update The {@link Update} to apply
* @param upsert flag to indicate if document should be upserted.
* @param multi flag to indicate if update might affect multiple documents.
* @return new instance of {@link UpdateOptions}.
*/
protected static UpdateOptions computeUpdateOptions(Query filterQuery, UpdateDefinition update, boolean upsert) {
protected UpdateOptions computeUpdateOptions(Query filterQuery, UpdateDefinition update, boolean upsert,
boolean multi) {
UpdateOptions options = new UpdateOptions();
options.upsert(upsert);
@ -207,6 +225,10 @@ abstract class BulkOperationsSupport { @@ -207,6 +225,10 @@ abstract class BulkOperationsSupport {
options.arrayFilters(list);
}
if (!multi && filterQuery.isSorted()) {
options.sort(filterQuery.getSortObject());
}
filterQuery.getCollation().map(Collation::toMongoCollation).ifPresent(options::collation);
return options;
}

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

@ -229,12 +229,14 @@ class DefaultBulkOperations extends BulkOperationsSupport implements BulkOperati @@ -229,12 +229,14 @@ class DefaultBulkOperations extends BulkOperationsSupport implements BulkOperati
ReplaceOptions replaceOptions = new ReplaceOptions();
replaceOptions.upsert(options.isUpsert());
if (query.isSorted()) {
replaceOptions.sort(query.getSortObject());
}
query.getCollation().map(Collation::toMongoCollation).ifPresent(replaceOptions::collation);
maybeEmitEvent(new BeforeConvertEvent<>(replacement, collectionName));
Object source = maybeInvokeBeforeConvertCallback(replacement);
addModel(source,
new ReplaceOneModel<>(getMappedQuery(query.getQueryObject()), getMappedObject(source), replaceOptions));
addModel(source, new ReplaceOneModel<>(query.getQueryObject(), getMappedObject(source), replaceOptions));
return this;
}
@ -315,7 +317,7 @@ class DefaultBulkOperations extends BulkOperationsSupport implements BulkOperati @@ -315,7 +317,7 @@ class DefaultBulkOperations extends BulkOperationsSupport implements BulkOperati
Assert.notNull(query, "Query must not be null");
Assert.notNull(update, "Update must not be null");
UpdateOptions options = computeUpdateOptions(query, update, upsert);
UpdateOptions options = computeUpdateOptions(query, update, upsert, multi);
if (multi) {
addModel(update, new UpdateManyModel<>(query.getQueryObject(), update.getUpdateObject(), options));

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

@ -189,13 +189,16 @@ class DefaultReactiveBulkOperations extends BulkOperationsSupport implements Rea @@ -189,13 +189,16 @@ class DefaultReactiveBulkOperations extends BulkOperationsSupport implements Rea
ReplaceOptions replaceOptions = new ReplaceOptions();
replaceOptions.upsert(options.isUpsert());
if (query.isSorted()) {
replaceOptions.sort(query.getSortObject());
}
query.getCollation().map(Collation::toMongoCollation).ifPresent(replaceOptions::collation);
this.models.add(Mono.just(replacement).flatMap(it -> {
maybeEmitEvent(new BeforeConvertEvent<>(it, collectionName));
return maybeInvokeBeforeConvertCallback(it);
}).map(it -> new SourceAwareWriteModelHolder(it,
new ReplaceOneModel<>(getMappedQuery(query.getQueryObject()), getMappedObject(it), replaceOptions))));
new ReplaceOneModel<>(query.getQueryObject(), getMappedObject(it), replaceOptions))));
return this;
}
@ -265,7 +268,7 @@ class DefaultReactiveBulkOperations extends BulkOperationsSupport implements Rea @@ -265,7 +268,7 @@ class DefaultReactiveBulkOperations extends BulkOperationsSupport implements Rea
Assert.notNull(query, "Query must not be null");
Assert.notNull(update, "Update must not be null");
UpdateOptions options = computeUpdateOptions(query, update, upsert);
UpdateOptions options = computeUpdateOptions(query, update, upsert, multi);
this.models.add(Mono.just(update).map(it -> {
if (multi) {

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

@ -25,6 +25,7 @@ import java.util.stream.Stream; @@ -25,6 +25,7 @@ import java.util.stream.Stream;
import org.bson.Document;
import org.springframework.data.domain.KeysetScrollPosition;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Window;
import org.springframework.data.geo.GeoResults;
import org.springframework.data.mongodb.core.BulkOperations.BulkMode;
@ -1604,8 +1605,9 @@ public interface MongoOperations extends FluentMongoOperations { @@ -1604,8 +1605,9 @@ public interface MongoOperations extends FluentMongoOperations {
* A potential {@link org.springframework.data.annotation.Version} property of the {@literal entityClass} will be
* auto-incremented if not explicitly specified in the update.
*
* @param query the query document that specifies the criteria used to select a document to be updated. Must not be
* {@literal null}.
* @param query the query document that specifies the criteria used to select a document to be updated. The
* {@link Query} may define a {@link Query#with(Sort) sort order} to influence which document to update when
* potentially matching multiple candidates. Must not be {@literal null}.
* @param update the {@link UpdateDefinition} that contains the updated object or {@code $} operators to manipulate
* the existing. Must not be {@literal null}.
* @param entityClass class that determines the collection to use.
@ -1623,12 +1625,11 @@ public interface MongoOperations extends FluentMongoOperations { @@ -1623,12 +1625,11 @@ public interface MongoOperations extends FluentMongoOperations {
* the provided updated document. <br />
* <strong>NOTE:</strong> Any additional support for field mapping, versions, etc. is not available due to the lack of
* domain type information. Use {@link #updateFirst(Query, UpdateDefinition, Class, String)} to get full type specific
* support. <br />
* <strong>NOTE:</strong> {@link Query#getSortObject() sorting} is not supported by {@code db.collection.updateOne}.
* Use {@link #findAndModify(Query, UpdateDefinition, Class, String)} instead.
* support.
*
* @param query the query document that specifies the criteria used to select a document to be updated. Must not be
* {@literal null}.
* @param query the query document that specifies the criteria used to select a document to be updated. The
* {@link Query} may define a {@link Query#with(Sort) sort order} to influence which document to update when
* potentially matching multiple candidates. Must not be {@literal null}.
* @param update the {@link UpdateDefinition} that contains the updated object or {@code $} 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}.
@ -1646,8 +1647,9 @@ public interface MongoOperations extends FluentMongoOperations { @@ -1646,8 +1647,9 @@ public interface MongoOperations extends FluentMongoOperations {
* A potential {@link org.springframework.data.annotation.Version} property of the {@literal entityClass} will be auto
* incremented if not explicitly specified in the update.
*
* @param query the query document that specifies the criteria used to select a document to be updated. Must not be
* {@literal null}.
* @param query the query document that specifies the criteria used to select a document to be updated. The
* {@link Query} may define a {@link Query#with(Sort) sort order} to influence which document to update when
* potentially matching multiple candidates. Must not be {@literal null}.
* @param update the {@link UpdateDefinition} that contains the updated object or {@code $} 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}.
@ -1833,7 +1835,8 @@ public interface MongoOperations extends FluentMongoOperations { @@ -1833,7 +1835,8 @@ public interface MongoOperations extends FluentMongoOperations {
*
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a document. The query may
* contain an index {@link Query#withHint(String) hint} or the {@link Query#collation(Collation) collation}
* to use. Must not be {@literal null}.
* to use. The {@link Query} may define a {@link Query#with(Sort) sort order} to influence which document to
* replace when potentially matching multiple candidates. Must not be {@literal null}.
* @param replacement the replacement document. Must not be {@literal null}.
* @return the {@link UpdateResult} which lets you access the results of the previous replacement.
* @throws org.springframework.data.mapping.MappingException if the collection name cannot be
@ -1850,7 +1853,8 @@ public interface MongoOperations extends FluentMongoOperations { @@ -1850,7 +1853,8 @@ public interface MongoOperations extends FluentMongoOperations {
*
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a document. The query may
* contain an index {@link Query#withHint(String) hint} or the {@link Query#collation(Collation) collation}
* to use. Must not be {@literal null}.
* to use. The {@link Query} may define a {@link Query#with(Sort) sort order} to influence which document to
* replace when potentially matching multiple candidates. Must not be {@literal null}.
* @param replacement the replacement document. Must not be {@literal null}.
* @param collectionName the collection to query. Must not be {@literal null}.
* @return the {@link UpdateResult} which lets you access the results of the previous replacement.
@ -1866,7 +1870,8 @@ public interface MongoOperations extends FluentMongoOperations { @@ -1866,7 +1870,8 @@ public interface MongoOperations extends FluentMongoOperations {
*
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a document.The query may
* contain an index {@link Query#withHint(String) hint} or the {@link Query#collation(Collation) collation}
* to use. Must not be {@literal null}.
* to use. The {@link Query} may define a {@link Query#with(Sort) sort order} to influence which document to
* replace when potentially matching multiple candidates. Must not be {@literal null}.
* @param replacement the replacement document. Must not be {@literal null}.
* @param options the {@link ReplaceOptions} holding additional information. Must not be {@literal null}.
* @return the {@link UpdateResult} which lets you access the results of the previous replacement.
@ -1884,7 +1889,8 @@ public interface MongoOperations extends FluentMongoOperations { @@ -1884,7 +1889,8 @@ public interface MongoOperations extends FluentMongoOperations {
*
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a document. The query may *
* contain an index {@link Query#withHint(String) hint} or the {@link Query#collation(Collation) collation}
* to use. Must not be {@literal null}.
* to use. The {@link Query} may define a {@link Query#with(Sort) sort order} to influence which document to
* replace when potentially matching multiple candidates. Must not be {@literal null}.
* @param replacement the replacement document. Must not be {@literal null}.
* @param options the {@link ReplaceOptions} holding additional information. Must not be {@literal null}.
* @return the {@link UpdateResult} which lets you access the results of the previous replacement.

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

@ -765,7 +765,7 @@ class QueryOperations { @@ -765,7 +765,7 @@ class QueryOperations {
}
if (query != null && query.isSorted()) {
options.sort(query.getSortObject());
options.sort(getMappedSort(domainType != null ? mappingContext.getPersistentEntity(domainType) : null));
}
HintFunction.from(getQuery().getHint()).ifPresent(codecRegistryProvider, options::hintString, options::hint);
@ -799,6 +799,9 @@ class QueryOperations { @@ -799,6 +799,9 @@ class QueryOperations {
options.collation(updateOptions.getCollation());
options.upsert(updateOptions.isUpsert());
applyHint(options::hintString, options::hint);
if (!isMulti() && getQuery().isSorted()) {
options.sort(getMappedSort(domainType != null ? mappingContext.getPersistentEntity(domainType) : null));
}
if (callback != null) {
callback.accept(options);

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

@ -19,6 +19,7 @@ import reactor.core.publisher.Mono; @@ -19,6 +19,7 @@ import reactor.core.publisher.Mono;
import java.util.List;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.UpdateDefinition;
@ -67,7 +68,8 @@ public interface ReactiveBulkOperations { @@ -67,7 +68,8 @@ public interface ReactiveBulkOperations {
/**
* Add a single update to the bulk operation. For the update request, only the first matching document is updated.
*
* @param query update criteria, must not be {@literal null}.
* @param query update criteria, must not be {@literal null}. The {@link Query} may define a {@link Query#with(Sort)
* sort order} to influence which document to update when potentially matching multiple candidates.
* @param update {@link UpdateDefinition} operation to perform, must not be {@literal null}.
* @return the current {@link ReactiveBulkOperations} instance with the update added, will never be {@literal null}.
*/
@ -111,8 +113,11 @@ public interface ReactiveBulkOperations { @@ -111,8 +113,11 @@ public interface ReactiveBulkOperations {
/**
* Add a single replace operation to the bulk operation.
*
* @param query Update criteria.
* @param replacement the replacement document. Must not be {@literal null}.
* @param query Replace criteria. The {@link Query} may define a {@link Query#with(Sort) sort order} to influence
* which document to replace when potentially matching multiple candidates.
* @param replacement the replacement document. Must not be {@literal null}. The {@link Query} may define a
* {@link Query#with(Sort) sort order} to influence which document to replace when potentially matching
* multiple candidates.
* @return the current {@link ReactiveBulkOperations} instance with the replace added, will never be {@literal null}.
*/
default ReactiveBulkOperations replaceOne(Query query, Object replacement) {
@ -122,7 +127,8 @@ public interface ReactiveBulkOperations { @@ -122,7 +127,8 @@ public interface ReactiveBulkOperations {
/**
* Add a single replace operation to the bulk operation.
*
* @param query Update criteria.
* @param query Replace criteria. The {@link Query} may define a {@link Query#with(Sort) sort order} to influence
* which document to replace when potentially matching multiple candidates.
* @param replacement the replacement document. Must not be {@literal null}.
* @param options the {@link FindAndModifyOptions} holding additional information. Must not be {@literal null}.
* @return the current {@link ReactiveBulkOperations} instance with the replace added, will never be {@literal null}.

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

@ -26,6 +26,7 @@ import org.bson.Document; @@ -26,6 +26,7 @@ import org.bson.Document;
import org.reactivestreams.Publisher;
import org.reactivestreams.Subscription;
import org.springframework.data.domain.KeysetScrollPosition;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Window;
import org.springframework.data.geo.GeoResult;
import org.springframework.data.mongodb.ReactiveMongoDatabaseFactory;
@ -1502,12 +1503,11 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations { @@ -1502,12 +1503,11 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
* the provided update document.
* <p>
* A potential {@link org.springframework.data.annotation.Version} property of the {@literal entityClass} will be
* auto-incremented if not explicitly specified in the update. <strong>NOTE:</strong> {@link Query#getSortObject()
* sorting} is not supported by {@code db.collection.updateOne}. Use
* {@link #findAndModify(Query, UpdateDefinition, Class)} instead.
* auto-incremented if not explicitly specified in the update.
*
* @param query the query document that specifies the criteria used to select a document to be updated. Must not be
* {@literal null}.
* @param query the query document that specifies the criteria used to select a document to be updated. The
* {@link Query} may define a {@link Query#with(Sort) sort order} to influence which document to update when
* potentially matching multiple candidates. Must not be {@literal null}.
* @param update the {@link UpdateDefinition} that contains the updated object or {@code $} operators to manipulate
* the existing. Must not be {@literal null}.
* @param entityClass class that determines the collection to use.
@ -1525,12 +1525,11 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations { @@ -1525,12 +1525,11 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
* the provided updated document. <br />
* <strong>NOTE:</strong> Any additional support for field mapping, versions, etc. is not available due to the lack of
* domain type information. Use {@link #updateFirst(Query, UpdateDefinition, Class, String)} to get full type specific
* support. <br />
* <strong>NOTE:</strong> {@link Query#getSortObject() sorting} is not supported by {@code db.collection.updateOne}.
* Use {@link #findAndModify(Query, UpdateDefinition, Class, String)} instead.
* support.
*
* @param query the query document that specifies the criteria used to select a document to be updated. Must not be
* {@literal null}.
* @param query the query document that specifies the criteria used to select a document to be updated. The
* {@link Query} may define a {@link Query#with(Sort) sort order} to influence which document to update when
* potentially matching multiple candidates. Must not be {@literal null}.
* @param update the {@link UpdateDefinition} that contains the updated object or {@code $} 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}.
@ -1548,8 +1547,9 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations { @@ -1548,8 +1547,9 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
* A potential {@link org.springframework.data.annotation.Version} property of the {@literal entityClass} will be
* auto-incremented if not explicitly specified in the update.
*
* @param query the query document that specifies the criteria used to select a document to be updated. Must not be
* {@literal null}.
* @param query the query document that specifies the criteria used to select a document to be updated. The
* {@link Query} may define a {@link Query#with(Sort) sort order} to influence which document to update when
* potentially matching multiple candidates. Must not be {@literal null}.
* @param update the {@link UpdateDefinition} that contains the updated object or {@code $} 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}.
@ -1745,7 +1745,8 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations { @@ -1745,7 +1745,8 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
*
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a document. The query may
* contain an index {@link Query#withHint(String) hint} or the {@link Query#collation(Collation) collation}
* to use. Must not be {@literal null}.
* to use. The {@link Query} may define a {@link Query#with(Sort) sort order} to influence which document to
* replace when potentially matching multiple candidates. Must not be {@literal null}.
* @param replacement the replacement document. Must not be {@literal null}.
* @return the {@link UpdateResult} which lets you access the results of the previous replacement.
* @throws org.springframework.data.mapping.MappingException if the collection name cannot be
@ -1762,7 +1763,8 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations { @@ -1762,7 +1763,8 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
*
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a document. The query may
* contain an index {@link Query#withHint(String) hint} or the {@link Query#collation(Collation) collation}
* to use. Must not be {@literal null}.
* to use. The {@link Query} may define a {@link Query#with(Sort) sort order} to influence which document to
* replace when potentially matching multiple candidates. Must not be {@literal null}.
* @param replacement the replacement document. Must not be {@literal null}.
* @param collectionName the collection to query. Must not be {@literal null}.
* @return the {@link UpdateResult} which lets you access the results of the previous replacement.
@ -1778,7 +1780,8 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations { @@ -1778,7 +1780,8 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
*
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a document.The query may
* contain an index {@link Query#withHint(String) hint} or the {@link Query#collation(Collation) collation}
* to use. Must not be {@literal null}.
* to use. The {@link Query} may define a {@link Query#with(Sort) sort order} to influence which document to
* replace when potentially matching multiple candidates. Must not be {@literal null}.
* @param replacement the replacement document. Must not be {@literal null}.
* @param options the {@link ReplaceOptions} holding additional information. Must not be {@literal null}.
* @return the {@link UpdateResult} which lets you access the results of the previous replacement.
@ -1796,7 +1799,8 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations { @@ -1796,7 +1799,8 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
*
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a document. The query may *
* contain an index {@link Query#withHint(String) hint} or the {@link Query#collation(Collation) collation}
* to use. Must not be {@literal null}.
* to use. The {@link Query} may define a {@link Query#with(Sort) sort order} to influence which document to
* replace when potentially matching multiple candidates. Must not be {@literal null}.
* @param replacement the replacement document. Must not be {@literal null}.
* @param options the {@link ReplaceOptions} holding additional information. Must not be {@literal null}.
* @return the {@link UpdateResult} which lets you access the results of the previous replacement.

51
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultBulkOperationsIntegrationTests.java

@ -16,6 +16,7 @@ @@ -16,6 +16,7 @@
package org.springframework.data.mongodb.core;
import static org.assertj.core.api.Assertions.*;
import static org.springframework.data.domain.Sort.Direction.DESC;
import java.util.ArrayList;
import java.util.Arrays;
@ -30,17 +31,20 @@ import org.junit.jupiter.api.extension.ExtendWith; @@ -30,17 +31,20 @@ import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.BulkOperationException;
import org.springframework.data.mongodb.core.BulkOperations.BulkMode;
import org.springframework.data.mongodb.core.DefaultBulkOperations.BulkOperationContext;
import org.springframework.data.mongodb.core.aggregation.AggregationUpdate;
import org.springframework.data.mongodb.core.convert.QueryMapper;
import org.springframework.data.mongodb.core.convert.UpdateMapper;
import org.springframework.data.mongodb.core.mapping.Field;
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
import org.springframework.data.mongodb.core.query.Criteria;
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.test.util.EnableIfMongoServerVersion;
import org.springframework.data.mongodb.test.util.MongoTemplateExtension;
import org.springframework.data.mongodb.test.util.MongoTestTemplate;
import org.springframework.data.mongodb.test.util.Template;
@ -323,6 +327,39 @@ public class DefaultBulkOperationsIntegrationTests { @@ -323,6 +327,39 @@ public class DefaultBulkOperationsIntegrationTests {
assertThat(doc).isInstanceOf(SpecialDoc.class);
}
@Test // GH-4797
@EnableIfMongoServerVersion(isGreaterThanEqual = "8.0")
public void updateShouldConsiderSorting() {
insertSomeDocuments();
BulkWriteResult result = createBulkOps(BulkMode.ORDERED, BaseDocWithRenamedField.class)
.updateOne(new Query().with(Sort.by(DESC, "renamedField")), new Update().set("bsky", "altnps")).execute();
assertThat(result.getModifiedCount()).isOne();
Document raw = operations.execute(COLLECTION_NAME, col -> col.find(new Document("_id", "4")).first());
assertThat(raw).containsEntry("bsky", "altnps");
}
@Test // GH-4797
@EnableIfMongoServerVersion(isGreaterThanEqual = "8.0")
public void replaceShouldConsiderSorting() {
insertSomeDocuments();
BaseDocWithRenamedField target = new BaseDocWithRenamedField();
target.value = "replacement";
BulkWriteResult result = createBulkOps(BulkMode.ORDERED, BaseDocWithRenamedField.class)
.replaceOne(new Query().with(Sort.by(DESC, "renamedField")), target).execute();
assertThat(result.getModifiedCount()).isOne();
Document raw = operations.execute(COLLECTION_NAME, col -> col.find(new Document("_id", "4")).first());
assertThat(raw).containsEntry("value", target.value);
}
private void testUpdate(BulkMode mode, boolean multi, int expectedUpdates) {
BulkOperations bulkOps = createBulkOps(mode);
@ -384,10 +421,10 @@ public class DefaultBulkOperationsIntegrationTests { @@ -384,10 +421,10 @@ public class DefaultBulkOperationsIntegrationTests {
final MongoCollection<Document> coll = operations.getCollection(COLLECTION_NAME);
coll.insertOne(rawDoc("1", "value1"));
coll.insertOne(rawDoc("2", "value1"));
coll.insertOne(rawDoc("3", "value2"));
coll.insertOne(rawDoc("4", "value2"));
coll.insertOne(rawDoc("1", "value1").append("rn_f", "001"));
coll.insertOne(rawDoc("2", "value1").append("rn_f", "002"));
coll.insertOne(rawDoc("3", "value2").append("rn_f", "003"));
coll.insertOne(rawDoc("4", "value2").append("rn_f", "004"));
}
private static Stream<Arguments> upsertArguments() {
@ -421,4 +458,10 @@ public class DefaultBulkOperationsIntegrationTests { @@ -421,4 +458,10 @@ public class DefaultBulkOperationsIntegrationTests {
private static Document rawDoc(String id, String value) {
return new Document("_id", id).append("value", value);
}
static class BaseDocWithRenamedField extends BaseDoc {
@Field("rn_f")
String renamedField;
}
}

49
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultReactiveBulkOperationsTests.java

@ -16,7 +16,11 @@ @@ -16,7 +16,11 @@
package org.springframework.data.mongodb.core;
import static org.assertj.core.api.Assertions.*;
import static org.springframework.data.domain.Sort.Direction.DESC;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.mapping.Field;
import org.springframework.data.mongodb.test.util.EnableIfMongoServerVersion;
import reactor.core.publisher.Flux;
import reactor.test.StepVerifier;
@ -289,11 +293,48 @@ class DefaultReactiveBulkOperationsTests { @@ -289,11 +293,48 @@ class DefaultReactiveBulkOperationsTests {
}).verifyComplete();
}
@Test // GH-4797
@EnableIfMongoServerVersion(isGreaterThanEqual = "8.0")
public void updateShouldConsiderSorting() {
insertSomeDocuments();
createBulkOps(BulkMode.ORDERED, BaseDocWithRenamedField.class) //
.updateOne(new Query().with(Sort.by(DESC, "renamedField")), new Update().set("bsky", "altnps")).execute() //
.as(StepVerifier::create) //
.consumeNextWith(result -> assertThat(result.getModifiedCount()).isOne()) //
.verifyComplete();
template.execute(COLLECTION_NAME, col -> col.find(new Document("_id", "4")).first()).as(StepVerifier::create) //
.consumeNextWith(raw -> assertThat(raw).containsEntry("bsky", "altnps")) //
.verifyComplete();
}
@Test // GH-4797
@EnableIfMongoServerVersion(isGreaterThanEqual = "8.0")
public void replaceShouldConsiderSorting() {
insertSomeDocuments();
BaseDocWithRenamedField target = new BaseDocWithRenamedField();
target.value = "replacement";
createBulkOps(BulkMode.ORDERED, BaseDocWithRenamedField.class) //
.replaceOne(new Query().with(Sort.by(DESC, "renamedField")), target).execute() //
.as(StepVerifier::create) //
.consumeNextWith(result -> assertThat(result.getModifiedCount()).isOne()) //
.verifyComplete();
template.execute(COLLECTION_NAME, col -> col.find(new Document("_id", "4")).first()).as(StepVerifier::create) //
.consumeNextWith(raw -> assertThat(raw).containsEntry("value", target.value)) //
.verifyComplete();
}
private void insertSomeDocuments() {
template.execute(COLLECTION_NAME, collection -> {
return Flux.from(collection.insertMany(
List.of(rawDoc("1", "value1"), rawDoc("2", "value1"), rawDoc("3", "value2"), rawDoc("4", "value2"))));
List.of(rawDoc("1", "value1").append("rn_f", "001"), rawDoc("2", "value1").append("rn_f", "002"), rawDoc("3", "value2").append("rn_f", "003"), rawDoc("4", "value2").append("rn_f", "004"))));
}).then().as(StepVerifier::create).verifyComplete();
}
@ -345,4 +386,10 @@ class DefaultReactiveBulkOperationsTests { @@ -345,4 +386,10 @@ class DefaultReactiveBulkOperationsTests {
private static Document rawDoc(String id, String value) {
return new Document("_id", id).append("value", value);
}
static class BaseDocWithRenamedField extends BaseDoc {
@Field("rn_f")
String renamedField;
}
}

15
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateReplaceTests.java

@ -34,9 +34,13 @@ import org.junit.jupiter.api.BeforeEach; @@ -34,9 +34,13 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.mongodb.core.aggregation.AggregationUpdate;
import org.springframework.data.mongodb.core.mapping.Field;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.test.util.Client;
import org.springframework.data.mongodb.test.util.EnableIfMongoServerVersion;
import org.springframework.data.mongodb.test.util.MongoClientExtension;
import com.mongodb.client.MongoClient;
@ -171,6 +175,17 @@ public class MongoTemplateReplaceTests { @@ -171,6 +175,17 @@ public class MongoTemplateReplaceTests {
assertThat(document).containsEntry("r-name", "Pizza Rat's Pizzaria");
}
@Test // GH-4797
@EnableIfMongoServerVersion(isGreaterThanEqual = "8.0")
void replaceConsidersSort() {
UpdateResult result = template.replace(new Query().with(Sort.by(Direction.DESC, "name")), new Restaurant("resist", "Manhattan"));
assertThat(result.getModifiedCount()).isOne();
Document document = retrieve(collection -> collection.find(Filters.eq("_id", 2)).first());
assertThat(document).containsEntry("r-name", "resist");
}
void initTestData() {
List<Document> testData = Stream.of( //

58
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateTests.java

@ -4066,64 +4066,6 @@ public class MongoTemplateTests { @@ -4066,64 +4066,6 @@ public class MongoTemplateTests {
assertThat(loaded.mapValue).isEqualTo(sourceMap);
}
@Test // GH-4797
public void updateFirstWithSortingAscendingShouldUpdateCorrectEntities() {
PersonWithIdPropertyOfTypeObjectId youngPerson = new PersonWithIdPropertyOfTypeObjectId();
youngPerson.setId(new ObjectId());
youngPerson.setAge(27);
youngPerson.setFirstName("Dave");
template.save(youngPerson);
PersonWithIdPropertyOfTypeObjectId oldPerson = new PersonWithIdPropertyOfTypeObjectId();
oldPerson.setId(new ObjectId());
oldPerson.setAge(34);
oldPerson.setFirstName("Dave");
template.save(oldPerson);
template.updateFirst(query(where("firstName").is("Dave")).with(Sort.by(Direction.ASC, "age")),
update("firstName", "Mike"), PersonWithIdPropertyOfTypeObjectId.class);
PersonWithIdPropertyOfTypeObjectId oldPersonResult = template.findById(oldPerson.getId(),
PersonWithIdPropertyOfTypeObjectId.class);
assertThat(oldPersonResult).isNotNull();
assertThat(oldPersonResult.getFirstName()).isEqualTo("Dave");
PersonWithIdPropertyOfTypeObjectId youngPersonResult = template.findById(youngPerson.getId(),
PersonWithIdPropertyOfTypeObjectId.class);
assertThat(youngPersonResult).isNotNull();
assertThat(youngPersonResult.getFirstName()).isEqualTo("Mike");
}
@Test // GH-4797
public void updateFirstWithSortingDescendingShouldUpdateCorrectEntities() {
PersonWithIdPropertyOfTypeObjectId youngPerson = new PersonWithIdPropertyOfTypeObjectId();
youngPerson.setId(new ObjectId());
youngPerson.setAge(27);
youngPerson.setFirstName("Dave");
template.save(youngPerson);
PersonWithIdPropertyOfTypeObjectId oldPerson = new PersonWithIdPropertyOfTypeObjectId();
oldPerson.setId(new ObjectId());
oldPerson.setAge(34);
oldPerson.setFirstName("Dave");
template.save(oldPerson);
template.updateFirst(query(where("firstName").is("Dave")).with(Sort.by(Direction.DESC, "age")),
update("firstName", "Mike"), PersonWithIdPropertyOfTypeObjectId.class);
PersonWithIdPropertyOfTypeObjectId oldPersonResult = template.findById(oldPerson.getId(),
PersonWithIdPropertyOfTypeObjectId.class);
assertThat(oldPersonResult).isNotNull();
assertThat(oldPersonResult.getFirstName()).isEqualTo("Mike");
PersonWithIdPropertyOfTypeObjectId youngPersonResult = template.findById(youngPerson.getId(),
PersonWithIdPropertyOfTypeObjectId.class);
assertThat(youngPersonResult).isNotNull();
assertThat(youngPersonResult.getFirstName()).isEqualTo("Dave");
}
private AtomicReference<ImmutableVersioned> createAfterSaveReference() {
AtomicReference<ImmutableVersioned> saved = new AtomicReference<>();

54
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateUpdateTests.java

@ -15,19 +15,26 @@ @@ -15,19 +15,26 @@
*/
package org.springframework.data.mongodb.core;
import static org.assertj.core.api.Assertions.*;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.stream.Stream;
import com.mongodb.client.result.UpdateResult;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Version;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.mongodb.core.aggregation.AggregationUpdate;
import org.springframework.data.mongodb.core.aggregation.ArithmeticOperators;
import org.springframework.data.mongodb.core.aggregation.ReplaceWithOperation;
@ -37,6 +44,7 @@ import org.springframework.data.mongodb.core.mapping.Field; @@ -37,6 +44,7 @@ import org.springframework.data.mongodb.core.mapping.Field;
import org.springframework.data.mongodb.core.query.Criteria;
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.test.util.EnableIfMongoServerVersion;
import org.springframework.data.mongodb.test.util.MongoTemplateExtension;
import org.springframework.data.mongodb.test.util.MongoTestTemplate;
@ -289,6 +297,35 @@ class MongoTemplateUpdateTests { @@ -289,6 +297,35 @@ class MongoTemplateUpdateTests {
null);
}
@ParameterizedTest // GH-4797
@EnableIfMongoServerVersion(isGreaterThanEqual = "8.0")
@MethodSource("sortedUpdateBookArgs")
void updateFirstWithSort(Class<?> domainType, Sort sort, UpdateDefinition update) {
Book one = new Book();
one.id = 1;
one.isbn = "001 001 300";
one.title = "News isn't fake";
one.author = new Author("John", "Backus");
Book two = new Book();
two.id = 2;
two.title = "love is love";
two.isbn = "001 001 100";
two.author = new Author("Grace", "Hopper");
template.insertAll(Arrays.asList(one, two));
UpdateResult result = template.update(domainType) //
.inCollection(template.getCollectionName(Book.class))//
.matching(new Query().with(sort)).apply(update) //
.first();
assertThat(result.getModifiedCount()).isOne();
assertThat(collection(Book.class).find(new org.bson.Document("_id", two.id)).first()).containsEntry("title",
"Science is real!");
}
private List<org.bson.Document> all(Class<?> type) {
return collection(type).find(new org.bson.Document()).into(new ArrayList<>());
}
@ -297,6 +334,21 @@ class MongoTemplateUpdateTests { @@ -297,6 +334,21 @@ class MongoTemplateUpdateTests {
return template.getCollection(template.getCollectionName(type));
}
private static Stream<Arguments> sortedUpdateBookArgs() {
Update update = new Update().set("title", "Science is real!");
AggregationUpdate aggUpdate = AggregationUpdate.update().set("title").toValue("Science is real!");
return Stream.of( //
Arguments.of(Book.class, Sort.by(Direction.ASC, "isbn"), update), // typed, no field mapping
Arguments.of(Book.class, Sort.by(Direction.DESC, "author.lastname"), update), // typed, map `lastname`
Arguments.of(Book.class, Sort.by(Direction.DESC, "author.last"), update), // typed, raw field name
Arguments.of(Object.class, Sort.by(Direction.ASC, "isbn"), update), // untyped, requires raw field name
Arguments.of(Book.class, Sort.by(Direction.ASC, "isbn"), aggUpdate), // aggregation, no field mapping
Arguments.of(Book.class, Sort.by(Direction.DESC, "author.last"), aggUpdate) // aggregation, map `lastname`
);
}
@Document("scores")
static class Score {

22
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveMongoTemplateReplaceTests.java

@ -20,6 +20,7 @@ import static org.springframework.data.mongodb.core.ReplaceOptions.*; @@ -20,6 +20,7 @@ import static org.springframework.data.mongodb.core.ReplaceOptions.*;
import static org.springframework.data.mongodb.core.query.Criteria.*;
import static org.springframework.data.mongodb.core.query.Query.*;
import org.springframework.data.mongodb.test.util.EnableIfMongoServerVersion;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
@ -37,7 +38,10 @@ import org.junit.jupiter.api.Test; @@ -37,7 +38,10 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.reactivestreams.Publisher;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.mongodb.core.mapping.Field;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.test.util.Client;
import org.springframework.data.mongodb.test.util.MongoClientExtension;
@ -198,7 +202,23 @@ public class ReactiveMongoTemplateReplaceTests { @@ -198,7 +202,23 @@ public class ReactiveMongoTemplateReplaceTests {
retrieve(collection -> collection.find(Filters.eq("_id", 4)).first()).as(StepVerifier::create)
.consumeNextWith(document -> {
assertThat(document).containsEntry("r-name", "Pizza Rat's Pizzaria");
});
})
.verifyComplete();
}
@Test // GH-4797
@EnableIfMongoServerVersion(isGreaterThanEqual = "8.0")
void replaceConsidersSort() {
template.replace(new Query().with(Sort.by(Direction.DESC, "name")), new Restaurant("resist", "Manhattan")) //
.as(StepVerifier::create) //
.consumeNextWith(result -> assertThat(result.getModifiedCount()).isOne()) //
.verifyComplete();
retrieve(collection -> collection.find(Filters.eq("_id", 2)).first()).as(StepVerifier::create)
.consumeNextWith(document -> {
assertThat(document).containsEntry("r-name", "resist");
}).verifyComplete();
}
void initTestData() {

36
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveMongoTemplateTests.java

@ -1846,42 +1846,6 @@ public class ReactiveMongoTemplateTests { @@ -1846,42 +1846,6 @@ public class ReactiveMongoTemplateTests {
.verify();
}
@Test // GH-4797
public void updateFirstWithSortingAscendingShouldUpdateCorrectEntities() {
Person youngPerson = new Person("Dave", 27);
Person oldPerson = new Person("Dave", 34);
template.insertAll(List.of(youngPerson, oldPerson))
.then(template.updateFirst(new Query(where("firstName").is("Dave")).with(Sort.by(Direction.ASC, "age")),
new Update().set("firstName", "Carter"), Person.class))
.flatMapMany(p -> template.find(new Query().with(Sort.by(Direction.ASC, "age")), Person.class))
.as(StepVerifier::create) //
.consumeNextWith(actual -> {
assertThat(actual.getFirstName()).isEqualTo("Carter");
}).consumeNextWith(actual -> {
assertThat(actual.getFirstName()).isEqualTo("Dave");
}).verifyComplete();
}
@Test // GH-4797
public void updateFirstWithSortingDescendingShouldUpdateCorrectEntities() {
Person youngPerson = new Person("Dave", 27);
Person oldPerson = new Person("Dave", 34);
template.insertAll(List.of(youngPerson, oldPerson))
.then(template.updateFirst(new Query(where("firstName").is("Dave")).with(Sort.by(Direction.DESC, "age")),
new Update().set("firstName", "Carter"), Person.class))
.flatMapMany(p -> template.find(new Query().with(Sort.by(Direction.ASC, "age")), Person.class))
.as(StepVerifier::create) //
.consumeNextWith(actual -> {
assertThat(actual.getFirstName()).isEqualTo("Dave");
}).consumeNextWith(actual -> {
assertThat(actual.getFirstName()).isEqualTo("Carter");
}).verifyComplete();
}
private PersonWithAList createPersonWithAList(String firstname, int age) {
PersonWithAList p = new PersonWithAList();

57
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveMongoTemplateUpdateTests.java

@ -18,6 +18,7 @@ package org.springframework.data.mongodb.core; @@ -18,6 +18,7 @@ package org.springframework.data.mongodb.core;
import static org.assertj.core.api.Assertions.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import java.util.ArrayList;
@ -25,12 +26,18 @@ import java.util.Arrays; @@ -25,12 +26,18 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.stream.Stream;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Version;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.mongodb.core.aggregation.AggregationUpdate;
import org.springframework.data.mongodb.core.aggregation.ArithmeticOperators;
import org.springframework.data.mongodb.core.aggregation.ReplaceWithOperation;
@ -39,6 +46,8 @@ import org.springframework.data.mongodb.core.mapping.Document; @@ -39,6 +46,8 @@ import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.Field;
import org.springframework.data.mongodb.core.query.Criteria;
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.test.util.Client;
import org.springframework.data.mongodb.test.util.EnableIfMongoServerVersion;
import org.springframework.data.mongodb.test.util.MongoClientExtension;
@ -269,6 +278,39 @@ public class ReactiveMongoTemplateUpdateTests { @@ -269,6 +278,39 @@ public class ReactiveMongoTemplateUpdateTests {
}
@ParameterizedTest // GH-4797
@EnableIfMongoServerVersion(isGreaterThanEqual = "8.0")
@MethodSource("sortedUpdateBookArgs")
void updateFirstWithSort(Class<?> domainType, Sort sort, UpdateDefinition update) {
Book one = new Book();
one.id = 1;
one.isbn = "001 001 300";
one.title = "News isn't fake";
one.author = new Author("John", "Backus");
Book two = new Book();
two.id = 2;
two.title = "love is love";
two.isbn = "001 001 100";
two.author = new Author("Grace", "Hopper");
template.insertAll(Arrays.asList(one, two)).then().as(StepVerifier::create).verifyComplete();
template.update(domainType) //
.inCollection(template.getCollectionName(Book.class))//
.matching(new Query().with(sort)).apply(update) //
.first().as(StepVerifier::create) //
.assertNext(result -> assertThat(result.getModifiedCount()).isOne()) //
.verifyComplete();
Mono.from(collection(Book.class).find(new org.bson.Document("_id", two.id)).first()) //
.as(StepVerifier::create) //
.assertNext(document -> assertThat(document).containsEntry("title", "Science is real!")) //
.verifyComplete();
}
private Flux<org.bson.Document> all(Class<?> type) {
return Flux.from(collection(type).find(new org.bson.Document()));
}
@ -277,6 +319,21 @@ public class ReactiveMongoTemplateUpdateTests { @@ -277,6 +319,21 @@ public class ReactiveMongoTemplateUpdateTests {
return client.getDatabase(DB_NAME).getCollection(template.getCollectionName(type));
}
private static Stream<Arguments> sortedUpdateBookArgs() {
Update update = new Update().set("title", "Science is real!");
AggregationUpdate aggUpdate = AggregationUpdate.update().set("title").toValue("Science is real!");
return Stream.of( //
Arguments.of(Book.class, Sort.by(Direction.ASC, "isbn"), update), // typed, no field mapping
Arguments.of(Book.class, Sort.by(Direction.DESC, "author.lastname"), update), // typed, map `lastname`
Arguments.of(Book.class, Sort.by(Direction.DESC, "author.last"), update), // typed, raw field name
Arguments.of(Object.class, Sort.by(Direction.ASC, "isbn"), update), // untyped, requires raw field name
Arguments.of(Book.class, Sort.by(Direction.ASC, "isbn"), aggUpdate), // aggregation, no field mapping
Arguments.of(Book.class, Sort.by(Direction.DESC, "author.last"), aggUpdate) // aggregation, map `lastname`
);
}
@Document("scores")
static class Score {

2
src/main/antora/modules/ROOT/pages/mongodb/template-crud-operations.adoc

@ -356,7 +356,7 @@ Read more in the see xref:mongodb/template-crud-operations.adoc#mongo-template.o @@ -356,7 +356,7 @@ Read more in the see xref:mongodb/template-crud-operations.adoc#mongo-template.o
* *updateFirst*: Updates the first document that matches the query document criteria with the updated document.
* *updateMulti*: Updates all objects that match the query document criteria with the updated document.
WARNING: `updateFirst` does not support ordering. Please use xref:mongodb/template-crud-operations.adoc#mongo-template.find-and-upsert[findAndModify] to apply `Sort`.
WARNING: `updateFirst` does not support ordering for MongoDB Versions below 8.0. Running one of the older versions, please use xref:mongodb/template-crud-operations.adoc#mongo-template.find-and-upsert[findAndModify] to apply `Sort`.
NOTE: Index hints for the update operation can be provided via `Query.withHint(...)`.

Loading…
Cancel
Save