From d18466a6167ebc789f4fbbe5ff997ba8eee22228 Mon Sep 17 00:00:00 2001 From: Minsu Date: Sun, 3 Mar 2019 03:28:13 +0900 Subject: [PATCH] DATAMONGO-2218 - Add support for replaceOne operation in BulkOperations --- .../data/mongodb/core/BulkOperations.java | 10 ++++ .../mongodb/core/DefaultBulkOperations.java | 56 ++++++++++++------- ...DefaultBulkOperationsIntegrationTests.java | 39 +++++++++++++ .../core/DefaultBulkOperationsUnitTests.java | 35 ++++++++++-- 4 files changed, 115 insertions(+), 25 deletions(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/BulkOperations.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/BulkOperations.java index 50b323e483..992805bf98 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/BulkOperations.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/BulkOperations.java @@ -31,6 +31,7 @@ * * @author Tobias Trelle * @author Oliver Gierke + * @author Minsu Kim * @since 1.9 */ public interface BulkOperations { @@ -135,6 +136,15 @@ enum BulkMode { */ BulkOperations remove(List removes); + /** + * Add a single replace operation to the bulk operation. + * + * @param query Update criteria. + * @param document the document to replace, must not be {@literal null}. + * @return the current {@link BulkOperations} instance with the replace added, will never be {@literal null}. + */ + BulkOperations replaceOne(Query query, Object document); + /** * Execute all bulk operations using the default write concern. * diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultBulkOperations.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultBulkOperations.java index ea53f39f4f..affee77f6a 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultBulkOperations.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultBulkOperations.java @@ -15,18 +15,12 @@ */ package org.springframework.data.mongodb.core; +import com.mongodb.WriteConcern; +import com.mongodb.client.model.*; import lombok.NonNull; import lombok.Value; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Optional; -import java.util.stream.Collectors; - import org.bson.Document; import org.bson.conversions.Bson; -import org.springframework.dao.DataAccessException; import org.springframework.dao.support.PersistenceExceptionTranslator; import org.springframework.data.mongodb.core.convert.QueryMapper; import org.springframework.data.mongodb.core.convert.UpdateMapper; @@ -38,18 +32,11 @@ import org.springframework.lang.Nullable; import org.springframework.util.Assert; -import com.mongodb.BulkWriteException; -import com.mongodb.WriteConcern; -import com.mongodb.client.MongoCollection; -import com.mongodb.client.model.BulkWriteOptions; -import com.mongodb.client.model.DeleteManyModel; -import com.mongodb.client.model.DeleteOneModel; -import com.mongodb.client.model.DeleteOptions; -import com.mongodb.client.model.InsertOneModel; -import com.mongodb.client.model.UpdateManyModel; -import com.mongodb.client.model.UpdateOneModel; -import com.mongodb.client.model.UpdateOptions; -import com.mongodb.client.model.WriteModel; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; /** * Default implementation for {@link BulkOperations}. @@ -58,6 +45,7 @@ * @author Oliver Gierke * @author Christoph Strobl * @author Mark Paluch + * @author Minsu Kim * @since 1.9 */ class DefaultBulkOperations implements BulkOperations { @@ -266,6 +254,32 @@ public BulkOperations remove(List removes) { return this; } + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.BulkOperations#replaceOne(org.springframework.data.mongodb.core.query.Query, java.lang.Object) + */ + @Override + public BulkOperations replaceOne(Query query, Object document) { + + Assert.notNull(query, "Query must not be null!"); + Assert.notNull(document, "Document must not be null!"); + + ReplaceOptions replaceOptions = new ReplaceOptions(); + query.getCollation().map(Collation::toMongoCollation).ifPresent(replaceOptions::collation); + Bson mappedQuery = getMappedQuery(query.getQueryObject()); + + if (document instanceof Document) { + models.add(new ReplaceOneModel<>(mappedQuery, (Document) document, replaceOptions)); + return this; + } + + Document sink = new Document(); + mongoOperations.getConverter().write(document, sink); + models.add(new ReplaceOneModel<>(mappedQuery, sink, replaceOptions)); + + return this; + } + /* * (non-Javadoc) * @see org.springframework.data.mongodb.core.BulkOperations#executeBulk() @@ -274,7 +288,7 @@ public BulkOperations remove(List removes) { public com.mongodb.bulk.BulkWriteResult execute() { try { - + return mongoOperations.execute(collectionName, collection -> { return collection.bulkWrite(models.stream().map(this::mapWriteModel).collect(Collectors.toList()), bulkOptions); }); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultBulkOperationsIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultBulkOperationsIntegrationTests.java index b8d0168d5c..7c53b5e60b 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultBulkOperationsIntegrationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultBulkOperationsIntegrationTests.java @@ -53,6 +53,7 @@ * @author Tobias Trelle * @author Oliver Gierke * @author Christoph Strobl + * @author Minsu Kim */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:infrastructure.xml") @@ -200,6 +201,31 @@ public void removeUnordered() { testRemove(BulkMode.UNORDERED); } + @Test // DATAMONGO-2218 + public void replaceOneOrdered() { + testReplaceOne(BulkMode.ORDERED); + } + + @Test // DATAMONGO-2218 + public void replaceOneUnordered() { + testReplaceOne(BulkMode.UNORDERED); + } + + @Test // DATAMONGO-2218 + public void replaceOneDoesReplace() { + + insertSomeDocuments(); + + com.mongodb.bulk.BulkWriteResult result = createBulkOps(BulkMode.ORDERED).// + replaceOne(where("_id", "1"), rawDoc("1", "value2")).// + execute(); + + assertThat(result, notNullValue()); + assertThat(result.getMatchedCount(), is(1)); + assertThat(result.getModifiedCount(), is(1)); + assertThat(result.getInsertedCount(), is(0)); + } + /** * If working on the same set of documents, only an ordered bulk operation will yield predictable results. */ @@ -278,6 +304,19 @@ private void testRemove(BulkMode mode) { assertThat(createBulkOps(mode).remove(removes).execute().getDeletedCount(), is(3)); } + private void testReplaceOne(BulkMode mode) { + + BulkOperations bulkOps = createBulkOps(mode); + + insertSomeDocuments(); + + Query query = where("_id", "1"); + Document document = rawDoc("1", "value2"); + int modifiedCount = bulkOps.replaceOne(query, document).execute().getModifiedCount(); + + assertThat(modifiedCount, is(1)); + } + private BulkOperations createBulkOps(BulkMode mode) { return createBulkOps(mode, null); } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultBulkOperationsUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultBulkOperationsUnitTests.java index 9af425e4b6..d12e50abfa 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultBulkOperationsUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultBulkOperationsUnitTests.java @@ -26,6 +26,7 @@ import java.util.List; import java.util.Optional; +import com.mongodb.client.model.*; import org.bson.Document; import org.junit.Before; import org.junit.Test; @@ -51,16 +52,13 @@ import com.mongodb.client.MongoCollection; import com.mongodb.client.MongoDatabase; -import com.mongodb.client.model.DeleteManyModel; -import com.mongodb.client.model.UpdateManyModel; -import com.mongodb.client.model.UpdateOneModel; -import com.mongodb.client.model.WriteModel; /** * Unit tests for {@link DefaultBulkOperations}. * * @author Christoph Strobl * @author Mark Paluch + * @author Minsu Kim */ @RunWith(MockitoJUnitRunner.class) public class DefaultBulkOperationsUnitTests { @@ -133,6 +131,18 @@ public void removeShouldUseCollationWhenPresent() { .isEqualTo(com.mongodb.client.model.Collation.builder().locale("de").build()); } + @Test // DATAMONGO-2218 + public void replaceOneShouldUseCollationWhenPresent() { + + ops.replaceOne(new BasicQuery("{}").collation(Collation.of("de")), new SomeDomainType()).execute(); + + verify(collection).bulkWrite(captor.capture(), any()); + + assertThat(captor.getValue().get(0)).isInstanceOf(ReplaceOneModel.class); + assertThat(((ReplaceOneModel) captor.getValue().get(0)).getReplaceOptions().getCollation()) + .isEqualTo(com.mongodb.client.model.Collation.builder().locale("de").build()); + } + @Test // DATAMONGO-1678 public void bulkUpdateShouldMapQueryAndUpdateCorrectly() { @@ -156,6 +166,23 @@ public void bulkRemoveShouldMapQueryCorrectly() { assertThat(updateModel.getFilter()).isEqualTo(new Document("first_name", "danerys")); } + @Test // DATAMONGO-2218 + public void bulkReplaceOneShouldMapQueryCorrectly() { + + SomeDomainType replacement = new SomeDomainType(); + replacement.firstName = "Minsu"; + replacement.lastName = "Kim"; + + ops.replaceOne(query(where("firstName").is("danerys")), replacement).execute(); + + verify(collection).bulkWrite(captor.capture(), any()); + + ReplaceOneModel updateModel = (ReplaceOneModel) captor.getValue().get(0); + assertThat(updateModel.getFilter()).isEqualTo(new Document("first_name", "danerys")); + assertThat(updateModel.getReplacement().getString("first_name")).isEqualTo("Minsu"); + assertThat(updateModel.getReplacement().getString("lastName")).isEqualTo("Kim"); + } + class SomeDomainType { @Id String id;