From 7c8efdf280b4edcb0f2add08461ae0c27ba7a445 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Fri, 30 Jan 2026 13:36:21 +0100 Subject: [PATCH] Generify the API --- .../data/mongodb/core/BulkOperationBase.java | 48 ++++-- .../data/mongodb/core/BulkOperations.java | 2 +- .../mongodb/core/NamespaceBulkOperations.java | 41 ++--- .../core/NamespacedBulkOperationSupport.java | 46 ++++-- ...DefaultBulkOperationsIntegrationTests.java | 116 -------------- ...amespaceBulkOperationIntegrationTests.java | 146 ++++++++++++++++++ 6 files changed, 234 insertions(+), 165 deletions(-) create mode 100644 spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/NamespaceBulkOperationIntegrationTests.java diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/BulkOperationBase.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/BulkOperationBase.java index 4d14ae45e..01574ded0 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/BulkOperationBase.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/BulkOperationBase.java @@ -18,6 +18,8 @@ package org.springframework.data.mongodb.core; import java.util.List; import org.springframework.data.domain.Sort; +import org.springframework.data.mongodb.core.query.Criteria; +import org.springframework.data.mongodb.core.query.CriteriaDefinition; import org.springframework.data.mongodb.core.query.Query; import org.springframework.data.mongodb.core.query.Update; import org.springframework.data.mongodb.core.query.UpdateDefinition; @@ -27,7 +29,7 @@ import org.springframework.data.util.Pair; * @author Christoph Strobl * @since 2026/01 */ -public interface BulkOperationBase { +public interface BulkOperationBase { /** * Add a single insert to the bulk operation. @@ -35,7 +37,7 @@ public interface BulkOperationBase { * @param documents the document to insert, must not be {@literal null}. * @return the current {@link BulkOperations} instance with the insert added, will never be {@literal null}. */ - BulkOperationBase insert(Object documents); + BulkOperationBase insert(T documents); /** * Add a list of inserts to the bulk operation. @@ -43,7 +45,11 @@ public interface BulkOperationBase { * @param documents List of documents to insert, must not be {@literal null}. * @return the current {@link BulkOperations} instance with the insert added, will never be {@literal null}. */ - BulkOperationBase insert(List documents); + BulkOperationBase insert(List documents); + + default BulkOperationBase updateOne(CriteriaDefinition criteria, UpdateDefinition update) { + return updateOne(Query.query(criteria), update); + } /** * Add a single update to the bulk operation. For the update request, only the first matching document is updated. @@ -53,7 +59,7 @@ public interface BulkOperationBase { * @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}. */ - default BulkOperationBase updateOne(Query query, Update update) { + default BulkOperationBase updateOne(Query query, Update update) { return updateOne(query, (UpdateDefinition) update); } @@ -66,7 +72,7 @@ public interface BulkOperationBase { * @return the current {@link BulkOperations} instance with the update added, will never be {@literal null}. * @since 4.1 */ - BulkOperationBase updateOne(Query query, UpdateDefinition update); + BulkOperationBase updateOne(Query query, UpdateDefinition update); /** * Add a list of updates to the bulk operation. For each update request, only the first matching document is updated. @@ -74,8 +80,11 @@ public interface BulkOperationBase { * @param updates Update operations to perform. * @return the current {@link BulkOperations} instance with the update added, will never be {@literal null}. */ - BulkOperationBase updateOne(List> updates); + BulkOperationBase updateOne(List> updates); + default BulkOperationBase updateMulti(CriteriaDefinition criteria, UpdateDefinition update) { + return updateMulti(Query.query(criteria), update); + } /** * Add a single update to the bulk operation. For the update request, all matching documents are updated. * @@ -83,7 +92,7 @@ public interface BulkOperationBase { * @param update Update operation to perform. * @return the current {@link BulkOperations} instance with the update added, will never be {@literal null}. */ - default BulkOperationBase updateMulti(Query query, Update update) { + default BulkOperationBase updateMulti(Query query, Update update) { return updateMulti(query, (UpdateDefinition) update); } @@ -95,7 +104,7 @@ public interface BulkOperationBase { * @return the current {@link BulkOperations} instance with the update added, will never be {@literal null}. * @since 4.1 */ - BulkOperationBase updateMulti(Query query, UpdateDefinition update); + BulkOperationBase updateMulti(Query query, UpdateDefinition update); /** * Add a list of updates to the bulk operation. For each update request, all matching documents are updated. @@ -103,7 +112,11 @@ public interface BulkOperationBase { * @param updates Update operations to perform. * @return the current {@link BulkOperations} instance with the update added, will never be {@literal null}. */ - BulkOperationBase updateMulti(List> updates); + BulkOperationBase updateMulti(List> updates); + + default BulkOperationBase upsert(CriteriaDefinition criteria, UpdateDefinition update) { + return upsert(Query.query(criteria), update); + } /** * Add a single upsert to the bulk operation. An upsert is an update if the set of matching documents is not empty, @@ -113,7 +126,7 @@ public interface BulkOperationBase { * @param update Update operation to perform. * @return the current {@link BulkOperations} instance with the update added, will never be {@literal null}. */ - default BulkOperationBase upsert(Query query, Update update) { + default BulkOperationBase upsert(Query query, Update update) { return upsert(query, (UpdateDefinition) update); } @@ -126,7 +139,7 @@ public interface BulkOperationBase { * @return the current {@link BulkOperations} instance with the update added, will never be {@literal null}. * @since 4.1 */ - BulkOperationBase upsert(Query query, UpdateDefinition update); + BulkOperationBase upsert(Query query, UpdateDefinition update); /** * Add a list of upserts to the bulk operation. An upsert is an update if the set of matching documents is not empty, @@ -135,15 +148,18 @@ public interface BulkOperationBase { * @param updates Updates/insert operations to perform. * @return the current {@link BulkOperations} instance with the update added, will never be {@literal null}. */ - BulkOperationBase upsert(List> updates); + BulkOperationBase upsert(List> updates); + default BulkOperationBase remove(CriteriaDefinition criteria) { + return remove(Query.query(criteria)); + } /** * Add a single remove operation to the bulk operation. * * @param remove the {@link Query} to select the documents to be removed, must not be {@literal null}. * @return the current {@link BulkOperations} instance with the removal added, will never be {@literal null}. */ - BulkOperationBase remove(Query remove); + BulkOperationBase remove(Query remove); /** * Add a list of remove operations to the bulk operation. @@ -151,7 +167,7 @@ public interface BulkOperationBase { * @param removes the remove operations to perform, must not be {@literal null}. * @return the current {@link BulkOperations} instance with the removal added, will never be {@literal null}. */ - BulkOperationBase remove(List removes); + BulkOperationBase remove(List removes); /** * Add a single replace operation to the bulk operation. @@ -162,7 +178,7 @@ public interface BulkOperationBase { * @return the current {@link BulkOperations} instance with the replacement added, will never be {@literal null}. * @since 2.2 */ - default BulkOperationBase replaceOne(Query query, Object replacement) { + default BulkOperationBase replaceOne(Query query, Object replacement) { return replaceOne(query, replacement, FindAndReplaceOptions.empty()); } @@ -176,6 +192,6 @@ public interface BulkOperationBase { * @return the current {@link BulkOperations} instance with the replacement added, will never be {@literal null}. * @since 2.2 */ - BulkOperationBase replaceOne(Query query, Object replacement, FindAndReplaceOptions options); + BulkOperationBase replaceOne(Query query, Object replacement, FindAndReplaceOptions options); } 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 75419ffb4..d8624cbfa 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 @@ -49,7 +49,7 @@ import com.mongodb.bulk.BulkWriteResult; * @author Minsu Kim * @since 1.9 */ -public interface BulkOperations extends BulkOperationBase { +public interface BulkOperations extends BulkOperationBase { /** * Mode for bulk operation. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/NamespaceBulkOperations.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/NamespaceBulkOperations.java index 505a44362..cdadcc3b7 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/NamespaceBulkOperations.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/NamespaceBulkOperations.java @@ -32,6 +32,7 @@ package org.springframework.data.mongodb.core; import java.util.List; +import java.util.function.Consumer; import org.springframework.data.mongodb.core.query.Query; import org.springframework.data.mongodb.core.query.Update; @@ -44,55 +45,59 @@ import com.mongodb.client.model.bulk.ClientBulkWriteResult; * @author Christoph Strobl * @since 2026/01 */ -public interface NamespaceBulkOperations extends BulkOperationBase { +public interface NamespaceBulkOperations { - NamespaceAwareBulkOperations inCollection(Class type); + NamespaceAwareBulkOperations inCollection(Class type); - NamespaceAwareBulkOperations inCollection(String collection); + NamespaceAwareBulkOperations inCollection(Class type, Consumer> bulkActions); + + NamespaceAwareBulkOperations inCollection(String collection); + + NamespaceAwareBulkOperations inCollection(String collection, Consumer> bulkActions); NamespaceBulkOperations switchDatabase(String databaseName); - interface NamespaceAwareBulkOperations extends BulkOperationBase, NamespaceBulkOperations { + ClientBulkWriteResult execute(); - NamespaceAwareBulkOperations insert(Object document); + interface NamespaceAwareBulkOperations extends BulkOperationBase, NamespaceBulkOperations { + + NamespaceAwareBulkOperations insert(S document); @Override - NamespaceAwareBulkOperations insert(List documents); + NamespaceAwareBulkOperations insert(List documents); @Override - NamespaceAwareBulkOperations updateOne(Query query, UpdateDefinition update); + NamespaceAwareBulkOperations updateOne(Query query, UpdateDefinition update); @Override - NamespaceAwareBulkOperations updateOne(List> updates); + NamespaceAwareBulkOperations updateOne(List> updates); @Override - NamespaceAwareBulkOperations updateMulti(Query query, UpdateDefinition update); + NamespaceAwareBulkOperations updateMulti(Query query, UpdateDefinition update); @Override - NamespaceAwareBulkOperations updateMulti(List> updates); + NamespaceAwareBulkOperations updateMulti(List> updates); @Override - NamespaceAwareBulkOperations upsert(Query query, UpdateDefinition update); + NamespaceAwareBulkOperations upsert(Query query, UpdateDefinition update); @Override - NamespaceAwareBulkOperations upsert(List> updates); + NamespaceAwareBulkOperations upsert(List> updates); @Override - NamespaceAwareBulkOperations remove(Query remove); + NamespaceAwareBulkOperations remove(Query remove); @Override - NamespaceAwareBulkOperations remove(List removes); + NamespaceAwareBulkOperations remove(List removes); @Override - NamespaceAwareBulkOperations replaceOne(Query query, Object replacement, FindAndReplaceOptions options); + NamespaceAwareBulkOperations replaceOne(Query query, Object replacement, FindAndReplaceOptions options); @Override - default NamespaceAwareBulkOperations upsert(Query query, Update update) { + default NamespaceAwareBulkOperations upsert(Query query, Update update) { upsert(query, (UpdateDefinition) update); return this; } - - ClientBulkWriteResult execute(); } // NamespacedBulkOperations inNamespace(Namespace namespace); diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/NamespacedBulkOperationSupport.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/NamespacedBulkOperationSupport.java index 5f0275112..4e76ac36d 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/NamespacedBulkOperationSupport.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/NamespacedBulkOperationSupport.java @@ -35,6 +35,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.function.Consumer; import org.bson.Document; import org.jspecify.annotations.Nullable; @@ -76,7 +77,7 @@ import com.mongodb.internal.client.model.bulk.ConcreteClientUpdateOneOptions; /** * @author Christoph Strobl */ -class NamespacedBulkOperationSupport implements NamespaceAwareBulkOperations { +class NamespacedBulkOperationSupport implements NamespaceAwareBulkOperations { private final BulkMode bulkMode; private final NamespacedBulkOperationContext ctx; @@ -94,7 +95,7 @@ class NamespacedBulkOperationSupport implements NamespaceAwareBulkOperations { } @Override - public NamespaceAwareBulkOperations insert(Object document) { + public NamespaceAwareBulkOperations insert(T document) { Assert.notNull(document, "Document must not be null"); @@ -108,20 +109,20 @@ class NamespacedBulkOperationSupport implements NamespaceAwareBulkOperations { } @Override - public NamespaceAwareBulkOperations insert(List documents) { + public NamespaceAwareBulkOperations insert(List documents) { documents.forEach(this::insert); return this; } @Override - public NamespaceAwareBulkOperations updateOne(Query query, UpdateDefinition update) { + public NamespaceAwareBulkOperations updateOne(Query query, UpdateDefinition update) { update(currentNamespace, query, update, false, false); return this; } @Override - public NamespaceAwareBulkOperations updateOne(List> updates) { + public NamespaceAwareBulkOperations updateOne(List> updates) { for (Pair update : updates) { update(currentNamespace, update.getFirst(), update.getSecond(), false, false); @@ -131,14 +132,14 @@ class NamespacedBulkOperationSupport implements NamespaceAwareBulkOperations { } @Override - public NamespaceAwareBulkOperations updateMulti(Query query, UpdateDefinition update) { + public NamespaceAwareBulkOperations updateMulti(Query query, UpdateDefinition update) { update(currentNamespace, query, update, false, true); return this; } @Override - public NamespaceAwareBulkOperations updateMulti(List> updates) { + public NamespaceAwareBulkOperations updateMulti(List> updates) { for (Pair update : updates) { update(currentNamespace, update.getFirst(), update.getSecond(), false, true); @@ -148,13 +149,13 @@ class NamespacedBulkOperationSupport implements NamespaceAwareBulkOperations { } @Override - public NamespaceAwareBulkOperations upsert(Query query, UpdateDefinition update) { + public NamespaceAwareBulkOperations upsert(Query query, UpdateDefinition update) { update(currentNamespace, query, update, true, true); return this; } @Override - public NamespaceAwareBulkOperations upsert(List> updates) { + public NamespaceAwareBulkOperations upsert(List> updates) { for (Pair update : updates) { upsert(update.getFirst(), update.getSecond()); } @@ -162,7 +163,7 @@ class NamespacedBulkOperationSupport implements NamespaceAwareBulkOperations { } @Override - public NamespaceAwareBulkOperations remove(Query query) { + public NamespaceAwareBulkOperations remove(Query query) { ClientDeleteManyOptions deleteOptions = ClientDeleteManyOptions.clientDeleteManyOptions(); query.getCollation().map(Collation::toMongoCollation).ifPresent(deleteOptions::collation); @@ -174,7 +175,7 @@ class NamespacedBulkOperationSupport implements NamespaceAwareBulkOperations { } @Override - public NamespaceAwareBulkOperations remove(List removes) { + public NamespaceAwareBulkOperations remove(List removes) { for (Query query : removes) { remove(query); } @@ -183,7 +184,7 @@ class NamespacedBulkOperationSupport implements NamespaceAwareBulkOperations { } @Override - public NamespaceAwareBulkOperations replaceOne(Query query, Object replacement, FindAndReplaceOptions options) { + public NamespaceAwareBulkOperations replaceOne(Query query, Object replacement, FindAndReplaceOptions options) { Assert.notNull(query, "Query must not be null"); Assert.notNull(replacement, "Replacement must not be null"); Assert.notNull(options, "Options must not be null"); @@ -223,11 +224,29 @@ class NamespacedBulkOperationSupport implements NamespaceAwareBulkOperations { } @Override - public NamespaceAwareBulkOperations inCollection(Class type) { + public NamespaceAwareBulkOperations inCollection(Class type, Consumer> bulkActions) { + NamespaceAwareBulkOperations ops = inCollection(type); + bulkActions.accept(ops); + return ops; + } + + @Override + @SuppressWarnings("unchecked") + public NamespaceAwareBulkOperations inCollection(String collection, + Consumer> bulkActions) { + NamespaceAwareBulkOperations ops = inCollection(collection); + bulkActions.accept(ops); + return ops; + } + + @Override + @SuppressWarnings("unchecked") + public NamespaceAwareBulkOperations inCollection(Class type) { return inCollection(operations.getCollectionName(type)); } @Override + @SuppressWarnings({ "rawtypes", "unchecked" }) public NamespaceAwareBulkOperations inCollection(String collection) { this.currentNamespace = new Namespace(currentNamespace.database(), collection); @@ -281,7 +300,6 @@ class NamespacedBulkOperationSupport implements NamespaceAwareBulkOperations { * @param update the {@link Update} to perform, must not be {@literal null}. * @param upsert whether to upsert. * @param multi whether to issue a multi-update. - * @return the {@link BulkOperations} with the update registered. */ private void update(Namespace namespace, Query query, UpdateDefinition update, boolean upsert, boolean multi) { 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 985725109..978db434d 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 @@ -19,7 +19,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.springframework.data.domain.Sort.Direction.DESC; -import static org.springframework.data.mongodb.core.Namespace.namespace; import java.util.ArrayList; import java.util.Arrays; @@ -27,15 +26,12 @@ import java.util.List; import java.util.Optional; import java.util.stream.Stream; -import org.bson.BsonBoolean; -import org.bson.BsonInt32; import org.bson.Document; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; 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.Score; import org.springframework.data.domain.Sort; import org.springframework.data.mongodb.BulkOperationException; import org.springframework.data.mongodb.core.BulkOperations.BulkMode; @@ -56,15 +52,10 @@ import org.springframework.data.mongodb.test.util.Template; import org.springframework.data.util.Pair; import com.mongodb.MongoBulkWriteException; -import com.mongodb.MongoNamespace; import com.mongodb.WriteConcern; import com.mongodb.bulk.BulkWriteResult; import com.mongodb.client.MongoClient; import com.mongodb.client.MongoCollection; -import com.mongodb.client.MongoDatabase; -import com.mongodb.internal.client.model.bulk.ConcreteClientInsertOneModel; -import com.mongodb.internal.client.model.bulk.ConcreteClientNamespacedInsertOneModel; -import com.mongodb.internal.operation.ClientBulkWriteOperation; /** * Integration tests for {@link DefaultBulkOperations}. @@ -374,113 +365,6 @@ public class DefaultBulkOperationsIntegrationTests { assertThat(raw).containsEntry("value", target.value); } - @Test // GH-5087 - void bulkWriteMultipleCollections() { - - BaseDoc doc1 = new BaseDoc(); - doc1.id = "id-doc1"; - doc1.value = "value-doc1"; - - BaseDoc doc2 = new BaseDoc(); - doc2.id = "id-doc2"; - doc2.value = "value-doc2"; - - NamespaceBulkOperations bulkOps = operations.bulkOps(BulkMode.ORDERED); - bulkOps - .inCollection(BaseDoc.class) - .insert(doc1) - .insert(doc2) - .upsert(where("_id", "id-doc3"), new Update().set("value", "upserted")) - .inCollection(SpecialDoc.class) - .insert(new SpecialDoc()) - .execute(); - - } - - @Test // GH-5087 - void apiDesign() { - -// NamespaceBulkOperations bulkOps = operations.bulkOps(BulkMode.ORDERED); -// bulkOps -// .inCollection(User.class) -// .insert(List.of(new User())) -// .upsert(Query.query(Criteria.where("name").is("batman")), new Update().set("actor", "...")) -// .switchDatabase("db2") -// .inCollection("sql") -// .insert(List.of(new Document())); -// - - -// -// NamespaceBulkOperations bulkOps = operations.bulkOps(BulkMode.ORDERED); -// bulkOps -// .inNamespace(namespace("person")) -// .update() -// .one(Criteria.where("name").is("batman"), null) -// .one(Criteria.where("name").is("joker"), null) -// .insert() -// .many(List.of(new Person())) -// .inNamespace(namespace("log")) -// .insert() -// .one(new User()) -// .inNamespace(Score.class) -// .insert().many(List.of()) -// -// .execute(); - - -// NamespaceBulkOps bulkOps1 = null; -// bulkOps1.inNamespace(namespace("person")) -// .andInNamespace(namespace("log")); - - } - -// interface NamespaceBulkOps { -// NamespaceBoundBulkOps inNamespace(Namespace namespace); -// CollectionBound -// } - - @Test // GH-5087 - void exploreItOnClient() { - - ClientBulkWriteOperation op = null; - // op.execute() - - mongoClient.getDatabase("test").getCollection("pizzas").drop(); - mongoClient.getDatabase("test").getCollection("pizzaOrders").drop(); - - // command: - MongoDatabase db = operations.getDb(); - MongoTemplate template = operations; - - Document commandDocument = new Document("bulkWrite", new BsonInt32(1)).append("errorsOnly", BsonBoolean.TRUE) - .append("ordered", BsonBoolean.TRUE); - List bulkOperations = new ArrayList<>(); - bulkOperations - .add(Document.parse("{ insert: 0, document: { _id: 5, type: 'sausage', size: 'small', price: 12 } }")); - bulkOperations.add(Document.parse("{ insert: 1, document: { _id: 4, type: 'vegan cheese', number: 16 } }")); - commandDocument.put("ops", bulkOperations); - - List namespaceInfo = new ArrayList<>(); - namespaceInfo.add(Document.parse("{ns: '%s.pizzas'}".formatted(template.getDb().getName()))); - namespaceInfo.add(Document.parse("{ns: '%s.pizzaOrders'}".formatted(template.getDb().getName()))); - commandDocument.put("nsInfo", namespaceInfo); - - template.getMongoDatabaseFactory().getMongoDatabase("admin").runCommand(commandDocument); - } - - @Test - void hackSomePotentialApi() { - - MongoNamespace pizzasNamespace = new MongoNamespace(operations.getDb().getName(), "pizzas"); - - ConcreteClientNamespacedInsertOneModel insert1 = new ConcreteClientNamespacedInsertOneModel(pizzasNamespace, - new ConcreteClientInsertOneModel( - Document.parse("{ insert: 0, document: { _id: 5, type: 'sausage', size: 'small', price: 12 } }"))); - insert1.getNamespace(); - insert1.getModel(); - } - private void testUpdate(BulkMode mode, boolean multi, int expectedUpdates) { BulkOperations bulkOps = createBulkOps(mode); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/NamespaceBulkOperationIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/NamespaceBulkOperationIntegrationTests.java new file mode 100644 index 000000000..4a8f6437f --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/NamespaceBulkOperationIntegrationTests.java @@ -0,0 +1,146 @@ +/* + * Copyright 2026. the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Copyright 2026 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.mongodb.core; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.data.mongodb.core.query.Criteria.where; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.data.mongodb.core.BulkOperations.BulkMode; +import org.springframework.data.mongodb.core.query.Update; +import org.springframework.data.mongodb.test.util.Client; +import org.springframework.data.mongodb.test.util.MongoTestTemplate; +import org.springframework.data.mongodb.test.util.Template; + +import com.mongodb.client.MongoClient; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.model.bulk.ClientBulkWriteResult; + +/** + * @author Christoph Strobl + * @since 2026/01 + */ +public class NamespaceBulkOperationIntegrationTests { + + static final String COLLECTION_NAME = "bulk_ops"; + + @Client static MongoClient mongoClient; + + @Template(initialEntitySet = BaseDoc.class) // + static MongoTestTemplate operations; + + @BeforeEach + public void setUp() { + operations.flushDatabase(); + } + + @Test // GH-5087 + void bulkWriteMultipleCollections() { + + /* + NamespaceBulkOperations and NamespaceAwareBulkOperations are new apis that allow to do bulk operations for MongoDB via an api that allows to be used with multiple mongodb collections. + The existing BulkOperations API is tied to a single collection. + You need to create tests for the new API similar to the existing ones in DefaultBulkOperationsIntegrationTests. + The tests need to make sure operations are executed in different collections. + you can see an example of this and how to use the new API in NamespaceBulkOperationIntegrationTests + + + */ + + operations.flushDatabase(); + + BaseDoc doc1 = new BaseDoc(); + doc1.id = "id-doc1"; + doc1.value = "value-doc1"; + + BaseDoc doc2 = new BaseDoc(); + doc2.id = "id-doc2"; + doc2.value = "value-doc2"; + + NamespaceBulkOperations bulkOps = operations.bulkOps(BulkMode.ORDERED); + ClientBulkWriteResult result = bulkOps + .inCollection(BaseDoc.class, + ops -> ops.insert(doc1).insert(doc2).upsert(where("_id").is("id-doc3"), + new Update().set("value", "upserted"))) + .inCollection(SpecialDoc.class).insert(new SpecialDoc()).execute(); + + assertThat(result.getUpsertedCount()).isOne(); + assertThat(result.getInsertedCount()).isEqualTo(3); + + Long inBaseDocCollection = operations.execute(BaseDoc.class, MongoCollection::countDocuments); + Long inSpecialCollection = operations.execute(SpecialDoc.class, MongoCollection::countDocuments); + assertThat(inBaseDocCollection).isEqualTo(3L); + assertThat(inSpecialCollection).isOne(); + } + + // @Test // GH-5087 + // void exploreItOnClient() { + // + // ClientBulkWriteOperation op = null; + // // op.execute() + // + // mongoClient.getDatabase("test").getCollection("pizzas").drop(); + // mongoClient.getDatabase("test").getCollection("pizzaOrders").drop(); + // + // // command: + // MongoDatabase db = operations.getDb(); + // MongoTemplate template = operations; + // + // Document commandDocument = new Document("bulkWrite", new BsonInt32(1)).append("errorsOnly", BsonBoolean.TRUE) + // .append("ordered", BsonBoolean.TRUE); + // List bulkOperations = new ArrayList<>(); + // bulkOperations + // .add(Document.parse("{ insert: 0, document: { _id: 5, type: 'sausage', size: 'small', price: 12 } }")); + // bulkOperations.add(Document.parse("{ insert: 1, document: { _id: 4, type: 'vegan cheese', number: 16 } }")); + // commandDocument.put("ops", bulkOperations); + // + // List namespaceInfo = new ArrayList<>(); + // namespaceInfo.add(Document.parse("{ns: '%s.pizzas'}".formatted(template.getDb().getName()))); + // namespaceInfo.add(Document.parse("{ns: '%s.pizzaOrders'}".formatted(template.getDb().getName()))); + // commandDocument.put("nsInfo", namespaceInfo); + // + // template.getMongoDatabaseFactory().getMongoDatabase("admin").runCommand(commandDocument); + // } + // + // @Test + // void hackSomePotentialApi() { + // + // MongoNamespace pizzasNamespace = new MongoNamespace(operations.getDb().getName(), "pizzas"); + // + // ConcreteClientNamespacedInsertOneModel insert1 = new ConcreteClientNamespacedInsertOneModel(pizzasNamespace, + // new ConcreteClientInsertOneModel( + // Document.parse("{ insert: 0, document: { _id: 5, type: 'sausage', size: 'small', price: 12 } }"))); + // insert1.getNamespace(); + // insert1.getModel(); + // } +}