Skip to content

DATAMONGO-2218 - Add support for replaceOne operation in BulkOperations #655

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
*
* @author Tobias Trelle
* @author Oliver Gierke
* @author Minsu Kim
* @since 1.9
*/
public interface BulkOperations {
Expand Down Expand Up @@ -135,6 +136,15 @@ enum BulkMode {
*/
BulkOperations remove(List<Query> 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.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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}.
Expand All @@ -58,6 +45,7 @@
* @author Oliver Gierke
* @author Christoph Strobl
* @author Mark Paluch
* @author Minsu Kim
* @since 1.9
*/
class DefaultBulkOperations implements BulkOperations {
Expand Down Expand Up @@ -266,6 +254,32 @@ public BulkOperations remove(List<Query> 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()
Expand All @@ -274,7 +288,7 @@ public BulkOperations remove(List<Query> 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);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
* @author Tobias Trelle
* @author Oliver Gierke
* @author Christoph Strobl
* @author Minsu Kim
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:infrastructure.xml")
Expand Down Expand Up @@ -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.
*/
Expand Down Expand Up @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 {
Expand Down Expand Up @@ -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<Document>) captor.getValue().get(0)).getReplaceOptions().getCollation())
.isEqualTo(com.mongodb.client.model.Collation.builder().locale("de").build());
}

@Test // DATAMONGO-1678
public void bulkUpdateShouldMapQueryAndUpdateCorrectly() {

Expand All @@ -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<Document> updateModel = (ReplaceOneModel<Document>) 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;
Expand Down