Browse Source

Add support for replaceOne operation.

Add replace methods to MongoOperations and MongoTemplate that allow to replace the first matching document with a given value.

Closes: #4462
Original Pull Request: #4463
pull/4498/head
Jakub 2 years ago committed by Christoph Strobl
parent
commit
f7549f739e
No known key found for this signature in database
GPG Key ID: 8CC1AB53391458C8
  1. 116
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoOperations.java
  2. 84
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java
  3. 2
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java
  4. 84
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReplaceOptions.java
  5. 16
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateTests.java
  6. 76
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateUnitTests.java

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

@ -1753,6 +1753,122 @@ public interface MongoOperations extends FluentMongoOperations { @@ -1753,6 +1753,122 @@ public interface MongoOperations extends FluentMongoOperations {
*/
<T> List<T> findAllAndRemove(Query query, Class<T> entityClass, String collectionName);
/**
* Triggers <a href="https://docs.mongodb.com/manual/reference/method/db.collection.replaceOne/">replaceOne</a> to
* replace a single document matching {@link Criteria} of given {@link Query} with the {@code replacement} document.
* <br />
* The collection name is derived from the {@literal replacement} type. <br />
* Options are defaulted to {@link ReplaceOptions#empty()}. <br />
* <strong>NOTE:</strong> The replacement entity must not hold an {@literal id}.
*
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
* fields specification. Must not be {@literal null}.
* @param 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
* {@link #getCollectionName(Class) derived} from the given replacement value.
*/
default <T> UpdateResult replace(Query query, T replacement) {
return replace(query, replacement, ReplaceOptions.empty());
}
/**
* Triggers <a href="https://docs.mongodb.com/manual/reference/method/db.collection.replaceOne/">replaceOne</a> to
* replace a single document matching {@link Criteria} of given {@link Query} with the {@code replacement}
* document.<br />
* Options are defaulted to {@link ReplaceOptions#empty()}. <br />
* <strong>NOTE:</strong> The replacement entity must not hold an {@literal id}.
*
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
* fields specification. Must not be {@literal null}.
* @param 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.
* @throws org.springframework.data.mapping.MappingException if the collection name cannot be
* {@link #getCollectionName(Class) derived} from the given replacement value.
*/
default <T> UpdateResult replace(Query query, T replacement, String collectionName) {
return replace(query, replacement, ReplaceOptions.empty(), collectionName);
}
/**
* Triggers <a href="https://docs.mongodb.com/manual/reference/method/db.collection.replaceOne/">replaceOne</a> to
* replace a single document matching {@link Criteria} of given {@link Query} with the {@code replacement} document
* taking {@link ReplaceOptions} into account.<br />
* <strong>NOTE:</strong> The replacement entity must not hold an {@literal id}.
*
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
* fields specification. Must not be {@literal null}.
* @param replacement the replacement document. Must not be {@literal null}.
* @param options the {@link FindAndModifyOptions} holding additional information. 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
* {@link #getCollectionName(Class) derived} from the given replacement value.
*/
default <T> UpdateResult replace(Query query, T replacement, ReplaceOptions options) {
return replace(query, replacement, options, getCollectionName(ClassUtils.getUserClass(replacement)));
}
/**
* Triggers <a href="https://docs.mongodb.com/manual/reference/method/db.collection.replaceOne/">replaceOne</a> to
* replace a single document matching {@link Criteria} of given {@link Query} with the {@code replacement} document
* taking {@link ReplaceOptions} into account.<br />
* <strong>NOTE:</strong> The replacement entity must not hold an {@literal id}.
*
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
* fields specification. Must not be {@literal null}.
* @param replacement the replacement document. Must not be {@literal null}.
* @param options the {@link FindAndModifyOptions} holding additional information. 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
* {@link #getCollectionName(Class) derived} from the given replacement value.
*/
default <T> UpdateResult replace(Query query, T replacement, ReplaceOptions options, String collectionName) {
Assert.notNull(replacement, "Replacement must not be null");
return replace(query, replacement, options, (Class<T>) ClassUtils.getUserClass(replacement), collectionName);
}
/**
* Triggers <a href="https://docs.mongodb.com/manual/reference/method/db.collection.replaceOne/">replaceOne</a> to
* replace a single document matching {@link Criteria} of given {@link Query} with the {@code replacement} document
* taking {@link ReplaceOptions} into account.<br />
* <strong>NOTE:</strong> The replacement entity must not hold an {@literal id}.
*
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
* fields specification. Must not be {@literal null}.
* @param replacement the replacement document. Must not be {@literal null}.
* @param options the {@link FindAndModifyOptions} holding additional information. Must not be {@literal null}.
* @param entityType the type used for mapping the {@link Query} to domain type fields and deriving the collection
* from. 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
* {@link #getCollectionName(Class) derived} from the given replacement value.
*/
default <S> UpdateResult replace(Query query, S replacement, ReplaceOptions options, Class<S> entityType) {
return replace(query, replacement, options, entityType, getCollectionName(ClassUtils.getUserClass(entityType)));
}
/**
* Triggers <a href="https://docs.mongodb.com/manual/reference/method/db.collection.replaceOne/">replaceOne</a> to
* replace a single document matching {@link Criteria} of given {@link Query} with the {@code replacement} document
* taking {@link ReplaceOptions} into account.<br />
* <strong>NOTE:</strong> The replacement entity must not hold an {@literal id}.
*
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
* fields specification. Must not be {@literal null}.
* @param replacement the replacement document. Must not be {@literal null}.
* @param options the {@link FindAndModifyOptions} holding additional information. Must not be {@literal null}.
* @param entityType the type used for mapping the {@link Query} to domain type fields. 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.
* @throws org.springframework.data.mapping.MappingException if the collection name cannot be
* {@link #getCollectionName(Class) derived} from the given replacement value.
*/
<S> UpdateResult replace(Query query, S replacement, ReplaceOptions options, Class<S> entityType,
String collectionName);
/**
* Returns the underlying {@link MongoConverter}.
*

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

@ -178,6 +178,7 @@ import com.mongodb.client.result.UpdateResult; @@ -178,6 +178,7 @@ import com.mongodb.client.result.UpdateResult;
* @author Anton Barkan
* @author Bartłomiej Mazur
* @author Michael Krog
* @author Jakub Zurawa
*/
public class MongoTemplate
implements MongoOperations, ApplicationContextAware, IndexOperationsProvider, ReadPreferenceAware {
@ -1618,7 +1619,7 @@ public class MongoTemplate @@ -1618,7 +1619,7 @@ public class MongoTemplate
}
}
collectionToUse.replaceOne(filter, replacement, new ReplaceOptions().upsert(true));
collectionToUse.replaceOne(filter, replacement, new com.mongodb.client.model.ReplaceOptions().upsert(true));
}
return mapped.getId();
});
@ -1749,7 +1750,7 @@ public class MongoTemplate @@ -1749,7 +1750,7 @@ public class MongoTemplate
}
}
ReplaceOptions replaceOptions = updateContext.getReplaceOptions(entityClass);
com.mongodb.client.model.ReplaceOptions replaceOptions = updateContext.getReplaceOptions(entityClass);
return collection.replaceOne(filter, updateObj, replaceOptions);
} else {
return multi ? collection.updateMany(queryObj, updateObj, opts)
@ -3421,6 +3422,56 @@ public class MongoTemplate @@ -3421,6 +3422,56 @@ public class MongoTemplate
return mongoDbFactory;
}
@Override
public <S> UpdateResult replace(Query query, S replacement, ReplaceOptions options, Class<S> entityType,
String collectionName) {
Assert.notNull(query, "Query must not be null");
Assert.notNull(replacement, "Replacement must not be null");
Assert.notNull(options, "Options must not be null Use ReplaceOptions#empty() instead");
Assert.notNull(entityType, "EntityType must not be null");
Assert.notNull(collectionName, "CollectionName must not be null");
Assert.isTrue(query.getLimit() <= 1, "Query must not define a limit other than 1 ore none");
Assert.isTrue(query.getSkip() <= 0, "Query must not define skip");
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(entityType);
QueryContext queryContext = queryOperations.createQueryContext(query);
CollectionPreparerDelegate collectionPreparer = createDelegate(query);
Document mappedQuery = queryContext.getMappedQuery(entity);
replacement = maybeCallBeforeConvert(replacement, collectionName);
Document mappedReplacement = operations.forEntity(replacement).toMappedDocument(this.mongoConverter).getDocument();
maybeCallBeforeSave(replacement, mappedReplacement, collectionName);
maybeEmitEvent(new BeforeSaveEvent<>(replacement, mappedReplacement, collectionName));
maybeCallBeforeSave(replacement, mappedReplacement, collectionName);
UpdateResult result = doReplace(options, entityType, collectionName, queryContext, collectionPreparer, mappedQuery,
mappedReplacement);
if (result.wasAcknowledged()) {
maybeEmitEvent(new AfterSaveEvent<>(replacement, mappedReplacement, collectionName));
maybeCallAfterSave(replacement, mappedReplacement, collectionName);
}
return result;
}
private <S> UpdateResult doReplace(ReplaceOptions options, Class<S> entityType, String collectionName,
QueryContext queryContext, CollectionPreparerDelegate collectionPreparer, Document mappedQuery,
Document replacement) {
ReplaceCallback replaceCallback = new ReplaceCallback(collectionPreparer, mappedQuery, replacement,
queryContext.getCollation(entityType).orElse(null), options);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(
String.format("findAndReplace using query: %s for class: %s and replacement: %s " + "in collection: %s",
serializeToJsonSafely(mappedQuery), entityType, serializeToJsonSafely(replacement), collectionName));
}
return execute(collectionName, replaceCallback);
}
/**
* A {@link CloseableIterator} that is backed by a MongoDB {@link MongoCollection}.
*
@ -3555,4 +3606,33 @@ public class MongoTemplate @@ -3555,4 +3606,33 @@ public class MongoTemplate
long countDocuments(CollectionPreparer collectionPreparer, String collection, Document filter,
CountOptions options);
}
private static class ReplaceCallback implements CollectionCallback<UpdateResult> {
private final CollectionPreparer<MongoCollection<Document>> collectionPreparer;
private final Document query;
private final Document update;
private final @Nullable com.mongodb.client.model.Collation collation;
private final ReplaceOptions options;
ReplaceCallback(CollectionPreparer<MongoCollection<Document>> collectionPreparer, Document query, Document update,
@Nullable com.mongodb.client.model.Collation collation, ReplaceOptions options) {
this.collectionPreparer = collectionPreparer;
this.query = query;
this.update = update;
this.options = options;
this.collation = collation;
}
@Override
public UpdateResult doInCollection(MongoCollection<Document> collection)
throws MongoException, DataAccessException {
com.mongodb.client.model.ReplaceOptions opts = new com.mongodb.client.model.ReplaceOptions();
opts.collation(collation);
opts.upsert(options.isUpsert());
return collectionPreparer.prepare(collection).replaceOne(query, update, opts);
}
}
}

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

@ -1782,7 +1782,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati @@ -1782,7 +1782,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
deferredFilter = Mono.just(filter);
}
ReplaceOptions replaceOptions = updateContext.getReplaceOptions(entityClass);
com.mongodb.client.model.ReplaceOptions replaceOptions = updateContext.getReplaceOptions(entityClass);
return deferredFilter.flatMap(it -> Mono.from(collectionToUse.replaceOne(it, updateObj, replaceOptions)));
}

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

@ -0,0 +1,84 @@ @@ -0,0 +1,84 @@
/**
* Options for
* <a href="https://docs.mongodb.com/manual/reference/method/db.collection.replaceOne/">replaceOne</a>.
* <br />
* Defaults to
* <dl>
* <dt>upsert</dt>
* <dd>false</dd>
* </dl>
*
* @author Jakub Zurawa
*/
package org.springframework.data.mongodb.core;
public class ReplaceOptions {
private boolean upsert;
private static final ReplaceOptions NONE = new ReplaceOptions() {
private static final String ERROR_MSG = "ReplaceOptions.none() cannot be changed; Please use ReplaceOptions.options() instead";
@Override
public ReplaceOptions upsert() {
throw new UnsupportedOperationException(ERROR_MSG);
}
};
/**
* Static factory method to create a {@link ReplaceOptions} instance.
* <dl>
* <dt>upsert</dt>
* <dd>false</dd>
* </dl>
*
* @return new instance of {@link ReplaceOptions}.
*/
public static ReplaceOptions options() {
return new ReplaceOptions();
}
/**
* Static factory method returning an unmodifiable {@link ReplaceOptions} instance.
*
* @return unmodifiable {@link ReplaceOptions} instance.
* @since 2.2
*/
public static ReplaceOptions none() {
return NONE;
}
/**
* Static factory method to create a {@link ReplaceOptions} instance with
* <dl>
* <dt>upsert</dt>
* <dd>false</dd>
* </dl>
*
* @return new instance of {@link ReplaceOptions}.
*/
public static ReplaceOptions empty() {
return new ReplaceOptions();
}
/**
* Insert a new document if not exists.
*
* @return this.
*/
public ReplaceOptions upsert() {
this.upsert = true;
return this;
}
/**
* Get the bit indicating if to create a new document if not exists.
*
* @return {@literal true} if set.
*/
public boolean isUpsert() {
return upsert;
}
}

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

@ -116,6 +116,7 @@ import com.mongodb.client.result.UpdateResult; @@ -116,6 +116,7 @@ import com.mongodb.client.result.UpdateResult;
* @author Mark Paluch
* @author Laszlo Csontos
* @author duozhilin
* @author Jakub Zurawa
*/
@ExtendWith(MongoClientExtension.class)
public class MongoTemplateTests {
@ -3872,6 +3873,21 @@ public class MongoTemplateTests { @@ -3872,6 +3873,21 @@ public class MongoTemplateTests {
assertThat(loaded).isEqualTo(source2);
}
@Test // GH-4300
public void replaceShouldReplaceDocument() {
org.bson.Document doc = new org.bson.Document("foo", "bar");
String collectionName = "replace";
template.save(doc, collectionName);
org.bson.Document replacement = new org.bson.Document("foo", "baz");
UpdateResult updateResult = template.replace(query(where("foo").is("bar")), replacement, ReplaceOptions.options(),
collectionName);
assertThat(updateResult.wasAcknowledged()).isTrue();
assertThat(template.findOne(query(where("foo").is("baz")), org.bson.Document.class, collectionName)).isNotNull();
}
private AtomicReference<ImmutableVersioned> createAfterSaveReference() {
AtomicReference<ImmutableVersioned> saved = new AtomicReference<>();

76
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateUnitTests.java

@ -131,6 +131,7 @@ import com.mongodb.client.result.UpdateResult; @@ -131,6 +131,7 @@ import com.mongodb.client.result.UpdateResult;
* @author Michael J. Simons
* @author Roman Puchkovskiy
* @author Yadhukrishna S Pai
* @author Jakub Zurawa
*/
@MockitoSettings(strictness = Strictness.LENIENT)
public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
@ -173,7 +174,7 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests { @@ -173,7 +174,7 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
when(collection.aggregate(any(List.class), any())).thenReturn(aggregateIterable);
when(collection.withReadConcern(any())).thenReturn(collection);
when(collection.withReadPreference(any())).thenReturn(collection);
when(collection.replaceOne(any(), any(), any(ReplaceOptions.class))).thenReturn(updateResult);
when(collection.replaceOne(any(), any(), any(com.mongodb.client.model.ReplaceOptions.class))).thenReturn(updateResult);
when(collection.withWriteConcern(any())).thenReturn(collectionWithWriteConcern);
when(collection.distinct(anyString(), any(Document.class), any())).thenReturn(distinctIterable);
when(collectionWithWriteConcern.deleteOne(any(Bson.class), any())).thenReturn(deleteResult);
@ -845,8 +846,7 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests { @@ -845,8 +846,7 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
@Test // GH-4277
void findShouldUseReadConcernWhenPresent() {
template.find(new BasicQuery("{'foo' : 'bar'}").withReadConcern(ReadConcern.SNAPSHOT),
AutogenerateableId.class);
template.find(new BasicQuery("{'foo' : 'bar'}").withReadConcern(ReadConcern.SNAPSHOT), AutogenerateableId.class);
verify(collection).withReadConcern(ReadConcern.SNAPSHOT);
}
@ -1002,7 +1002,8 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests { @@ -1002,7 +1002,8 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
template.updateFirst(new BasicQuery("{}").collation(Collation.of("fr")), new Update(), AutogenerateableId.class);
ArgumentCaptor<ReplaceOptions> options = ArgumentCaptor.forClass(ReplaceOptions.class);
ArgumentCaptor<com.mongodb.client.model.ReplaceOptions> options = ArgumentCaptor
.forClass(com.mongodb.client.model.ReplaceOptions.class);
verify(collection).replaceOne(any(), any(), options.capture());
assertThat(options.getValue().getCollation().getLocale()).isEqualTo("fr");
@ -1129,8 +1130,7 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests { @@ -1129,8 +1130,7 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
void appliesFieldsWhenInterfaceProjectionIsClosedAndQueryDoesNotDefineFields() {
template.doFind(CollectionPreparer.identity(), "star-wars", new Document(), new Document(), Person.class,
PersonProjection.class,
CursorPreparer.NO_OP_PREPARER);
PersonProjection.class, CursorPreparer.NO_OP_PREPARER);
verify(findIterable).projection(eq(new Document("firstname", 1)));
}
@ -1139,8 +1139,7 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests { @@ -1139,8 +1139,7 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
void doesNotApplyFieldsWhenInterfaceProjectionIsClosedAndQueryDefinesFields() {
template.doFind(CollectionPreparer.identity(), "star-wars", new Document(), new Document("bar", 1), Person.class,
PersonProjection.class,
CursorPreparer.NO_OP_PREPARER);
PersonProjection.class, CursorPreparer.NO_OP_PREPARER);
verify(findIterable).projection(eq(new Document("bar", 1)));
}
@ -1149,8 +1148,7 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests { @@ -1149,8 +1148,7 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
void doesNotApplyFieldsWhenInterfaceProjectionIsOpen() {
template.doFind(CollectionPreparer.identity(), "star-wars", new Document(), new Document(), Person.class,
PersonSpELProjection.class,
CursorPreparer.NO_OP_PREPARER);
PersonSpELProjection.class, CursorPreparer.NO_OP_PREPARER);
verify(findIterable).projection(eq(BsonUtils.EMPTY_DOCUMENT));
}
@ -1159,8 +1157,7 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests { @@ -1159,8 +1157,7 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
void appliesFieldsToDtoProjection() {
template.doFind(CollectionPreparer.identity(), "star-wars", new Document(), new Document(), Person.class,
Jedi.class,
CursorPreparer.NO_OP_PREPARER);
Jedi.class, CursorPreparer.NO_OP_PREPARER);
verify(findIterable).projection(eq(new Document("firstname", 1)));
}
@ -1169,8 +1166,7 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests { @@ -1169,8 +1166,7 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
void doesNotApplyFieldsToDtoProjectionWhenQueryDefinesFields() {
template.doFind(CollectionPreparer.identity(), "star-wars", new Document(), new Document("bar", 1), Person.class,
Jedi.class,
CursorPreparer.NO_OP_PREPARER);
Jedi.class, CursorPreparer.NO_OP_PREPARER);
verify(findIterable).projection(eq(new Document("bar", 1)));
}
@ -1179,8 +1175,7 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests { @@ -1179,8 +1175,7 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
void doesNotApplyFieldsWhenTargetIsNotAProjection() {
template.doFind(CollectionPreparer.identity(), "star-wars", new Document(), new Document(), Person.class,
Person.class,
CursorPreparer.NO_OP_PREPARER);
Person.class, CursorPreparer.NO_OP_PREPARER);
verify(findIterable).projection(eq(BsonUtils.EMPTY_DOCUMENT));
}
@ -1189,8 +1184,7 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests { @@ -1189,8 +1184,7 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
void doesNotApplyFieldsWhenTargetExtendsDomainType() {
template.doFind(CollectionPreparer.identity(), "star-wars", new Document(), new Document(), Person.class,
PersonExtended.class,
CursorPreparer.NO_OP_PREPARER);
PersonExtended.class, CursorPreparer.NO_OP_PREPARER);
verify(findIterable).projection(eq(BsonUtils.EMPTY_DOCUMENT));
}
@ -1243,7 +1237,7 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests { @@ -1243,7 +1237,7 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
template.save(entity);
verify(collection, times(1)).replaceOne(queryCaptor.capture(), updateCaptor.capture(), any(ReplaceOptions.class));
verify(collection, times(1)).replaceOne(queryCaptor.capture(), updateCaptor.capture(), any(com.mongodb.client.model.ReplaceOptions.class));
assertThat(queryCaptor.getValue()).isEqualTo(new Document("_id", 1).append("version", 10));
assertThat(updateCaptor.getValue())
@ -1785,7 +1779,7 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests { @@ -1785,7 +1779,7 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
ArgumentCaptor<Document> captor = ArgumentCaptor.forClass(Document.class);
verify(collection).replaceOne(any(), captor.capture(), any(ReplaceOptions.class));
verify(collection).replaceOne(any(), captor.capture(), any(com.mongodb.client.model.ReplaceOptions.class));
assertThat(captor.getValue()).containsEntry("added-by", "callback");
}
@ -2005,7 +1999,7 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests { @@ -2005,7 +1999,7 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
@Test // DATAMONGO-2341
void saveShouldAppendNonDefaultShardKeyToVersionedEntityIfNotPresentInFilter() {
when(collection.replaceOne(any(), any(), any(ReplaceOptions.class)))
when(collection.replaceOne(any(), any(), any(com.mongodb.client.model.ReplaceOptions.class)))
.thenReturn(UpdateResult.acknowledged(1, 1L, null));
template.save(new ShardedVersionedEntityWithNonDefaultShardKey("id-1", 1L, "AT", 4230));
@ -2093,7 +2087,7 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests { @@ -2093,7 +2087,7 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
@Test // DATAMONGO-2341
void saveVersionedShouldProjectOnShardKeyWhenLoadingExistingDocument() {
when(collection.replaceOne(any(), any(), any(ReplaceOptions.class)))
when(collection.replaceOne(any(), any(), any(com.mongodb.client.model.ReplaceOptions.class)))
.thenReturn(UpdateResult.acknowledged(1, 1L, null));
when(findIterable.first()).thenReturn(new Document("_id", "id-1").append("country", "US").append("userid", 4230));
@ -2442,6 +2436,44 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests { @@ -2442,6 +2436,44 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
any(FindOneAndReplaceOptions.class));
}
@Test // GH-4300
void replaceShouldUseCollationWhenPresent() {
template.replace(new BasicQuery("{}").collation(Collation.of("fr")), new AutogenerateableId());
ArgumentCaptor<com.mongodb.client.model.ReplaceOptions> options = ArgumentCaptor
.forClass(com.mongodb.client.model.ReplaceOptions.class);
verify(collection).replaceOne(any(), any(), options.capture());
assertThat(options.getValue().isUpsert()).isFalse();
assertThat(options.getValue().getCollation().getLocale()).isEqualTo("fr");
}
@Test // GH-4300
void replaceShouldUpsert() {
template.replace(new BasicQuery("{}"), new Sith(), ReplaceOptions.options().upsert());
ArgumentCaptor<com.mongodb.client.model.ReplaceOptions> options = ArgumentCaptor
.forClass(com.mongodb.client.model.ReplaceOptions.class);
verify(collection).replaceOne(any(), any(), options.capture());
assertThat(options.getValue().isUpsert()).isTrue();
}
@Test // GH-4300
void replaceShouldUseDefaultCollationWhenPresent() {
template.replace(new BasicQuery("{}"), new Sith(), ReplaceOptions.options());
ArgumentCaptor<com.mongodb.client.model.ReplaceOptions> options = ArgumentCaptor
.forClass(com.mongodb.client.model.ReplaceOptions.class);
verify(collection).replaceOne(any(), any(), options.capture());
assertThat(options.getValue().getCollation().getLocale()).isEqualTo("de_AT");
}
class AutogenerateableId {
@Id BigInteger id;

Loading…
Cancel
Save