From 63362780f58afc7090e67a0fbd8a7f1206bdc7e4 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Tue, 10 Mar 2026 08:43:50 +0100 Subject: [PATCH 1/4] Prepare issue branch. --- pom.xml | 2 +- spring-data-mongodb-distribution/pom.xml | 2 +- spring-data-mongodb/pom.xml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index 3e4f4c3130..58b6fb7001 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-mongodb-parent - 5.1.0-SNAPSHOT + 5.1.0-4422-SNAPSHOT pom Spring Data MongoDB diff --git a/spring-data-mongodb-distribution/pom.xml b/spring-data-mongodb-distribution/pom.xml index cab02fe276..72ced03d84 100644 --- a/spring-data-mongodb-distribution/pom.xml +++ b/spring-data-mongodb-distribution/pom.xml @@ -15,7 +15,7 @@ org.springframework.data spring-data-mongodb-parent - 5.1.0-SNAPSHOT + 5.1.0-4422-SNAPSHOT ../pom.xml diff --git a/spring-data-mongodb/pom.xml b/spring-data-mongodb/pom.xml index 0a758111af..be8a8d72ae 100644 --- a/spring-data-mongodb/pom.xml +++ b/spring-data-mongodb/pom.xml @@ -13,7 +13,7 @@ org.springframework.data spring-data-mongodb-parent - 5.1.0-SNAPSHOT + 5.1.0-4422-SNAPSHOT ../pom.xml From f0d7c8aa6c52d627a372db890f4c0c6fce66a434 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Tue, 10 Mar 2026 08:49:48 +0100 Subject: [PATCH 2/4] Polishing. Simplify converters. --- .../mongodb/core/DefaultIndexOperations.java | 4 +- .../core/DefaultReactiveIndexOperations.java | 4 +- .../data/mongodb/core/IndexConverters.java | 132 +++++++----------- 3 files changed, 58 insertions(+), 82 deletions(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultIndexOperations.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultIndexOperations.java index 49eb75317f..8924d4ad77 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultIndexOperations.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultIndexOperations.java @@ -122,7 +122,7 @@ public String createIndex(IndexDefinition indexDefinition) { MongoPersistentEntity entity = lookupPersistentEntity(type, collectionName); - IndexOptions indexOptions = IndexConverters.indexDefinitionToIndexOptionsConverter().convert(indexDefinition); + IndexOptions indexOptions = IndexConverters.toIndexOptions(indexDefinition); indexOptions = addPartialFilterIfPresent(indexOptions, indexDefinition.getIndexOptions(), entity); indexOptions = addDefaultCollationIfRequired(indexOptions, entity); @@ -201,7 +201,7 @@ private List getIndexData(MongoCursor cursor) { while (cursor.hasNext()) { Document ix = cursor.next(); - IndexInfo indexInfo = IndexConverters.documentToIndexInfoConverter().convert(ix); + IndexInfo indexInfo = IndexInfo.indexInfoOf(ix); indexInfoList.add(indexInfo); } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultReactiveIndexOperations.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultReactiveIndexOperations.java index b3baf3e897..0e35a253d2 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultReactiveIndexOperations.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultReactiveIndexOperations.java @@ -90,7 +90,7 @@ public Mono createIndex(IndexDefinition indexDefinition) { MongoPersistentEntity entity = getConfiguredEntity(); - IndexOptions indexOptions = IndexConverters.indexDefinitionToIndexOptionsConverter().convert(indexDefinition); + IndexOptions indexOptions = IndexConverters.toIndexOptions(indexDefinition); indexOptions = addPartialFilterIfPresent(indexOptions, indexDefinition.getIndexOptions(), entity); indexOptions = addDefaultCollationIfRequired(indexOptions, entity); @@ -141,7 +141,7 @@ public Mono dropAllIndexes() { public Flux getIndexInfo() { return mongoOperations.execute(collectionName, collection -> collection.listIndexes(Document.class)) // - .map(IndexConverters.documentToIndexInfoConverter()::convert); + .map(IndexInfo::indexInfoOf); } private @Nullable MongoPersistentEntity getConfiguredEntity() { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/IndexConverters.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/IndexConverters.java index 9a8d7a1dcd..ff634a01e2 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/IndexConverters.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/IndexConverters.java @@ -19,9 +19,9 @@ import org.bson.Document; import org.jspecify.annotations.Nullable; + import org.springframework.core.convert.converter.Converter; import org.springframework.data.mongodb.core.index.IndexDefinition; -import org.springframework.data.mongodb.core.index.IndexInfo; import org.springframework.util.ObjectUtils; import com.mongodb.client.model.Collation; @@ -36,92 +36,72 @@ */ abstract class IndexConverters { - private static final Converter DEFINITION_TO_MONGO_INDEX_OPTIONS; - private static final Converter DOCUMENT_INDEX_INFO; - - static { - - DEFINITION_TO_MONGO_INDEX_OPTIONS = getIndexDefinitionIndexOptionsConverter(); - DOCUMENT_INDEX_INFO = getDocumentIndexInfoConverter(); - } - private IndexConverters() { } - static Converter indexDefinitionToIndexOptionsConverter() { - return DEFINITION_TO_MONGO_INDEX_OPTIONS; - } + static IndexOptions toIndexOptions(IndexDefinition indexDefinition) { - static Converter documentToIndexInfoConverter() { - return DOCUMENT_INDEX_INFO; - } - - private static Converter getIndexDefinitionIndexOptionsConverter() { - - return indexDefinition -> { + Document indexOptions = indexDefinition.getIndexOptions(); + IndexOptions ops = new IndexOptions(); - Document indexOptions = indexDefinition.getIndexOptions(); - IndexOptions ops = new IndexOptions(); - - if (indexOptions.containsKey("name")) { - ops = ops.name(indexOptions.get("name").toString()); - } - if (indexOptions.containsKey("unique")) { - ops = ops.unique((Boolean) indexOptions.get("unique")); - } - if (indexOptions.containsKey("sparse")) { - ops = ops.sparse((Boolean) indexOptions.get("sparse")); - } - if (indexOptions.containsKey("background")) { - ops = ops.background((Boolean) indexOptions.get("background")); - } - if (indexOptions.containsKey("expireAfterSeconds")) { - ops = ops.expireAfter((Long) indexOptions.get("expireAfterSeconds"), TimeUnit.SECONDS); - } - if (indexOptions.containsKey("min")) { - ops = ops.min(((Number) indexOptions.get("min")).doubleValue()); - } - if (indexOptions.containsKey("max")) { - ops = ops.max(((Number) indexOptions.get("max")).doubleValue()); - } - if (indexOptions.containsKey("bits")) { - ops = ops.bits((Integer) indexOptions.get("bits")); - } - if (indexOptions.containsKey("default_language")) { - ops = ops.defaultLanguage(indexOptions.get("default_language").toString()); - } - if (indexOptions.containsKey("language_override")) { - ops = ops.languageOverride(indexOptions.get("language_override").toString()); - } - if (indexOptions.containsKey("weights")) { - ops = ops.weights((org.bson.Document) indexOptions.get("weights")); - } + if (indexOptions.containsKey("name")) { + ops = ops.name(indexOptions.get("name").toString()); + } + if (indexOptions.containsKey("unique")) { + ops = ops.unique((Boolean) indexOptions.get("unique")); + } + if (indexOptions.containsKey("sparse")) { + ops = ops.sparse((Boolean) indexOptions.get("sparse")); + } + if (indexOptions.containsKey("background")) { + ops = ops.background((Boolean) indexOptions.get("background")); + } + if (indexOptions.containsKey("expireAfterSeconds")) { + ops = ops.expireAfter((Long) indexOptions.get("expireAfterSeconds"), TimeUnit.SECONDS); + } + if (indexOptions.containsKey("min")) { + ops = ops.min(((Number) indexOptions.get("min")).doubleValue()); + } + if (indexOptions.containsKey("max")) { + ops = ops.max(((Number) indexOptions.get("max")).doubleValue()); + } + if (indexOptions.containsKey("bits")) { + ops = ops.bits((Integer) indexOptions.get("bits")); + } + if (indexOptions.containsKey("default_language")) { + ops = ops.defaultLanguage(indexOptions.get("default_language").toString()); + } + if (indexOptions.containsKey("language_override")) { + ops = ops.languageOverride(indexOptions.get("language_override").toString()); + } + if (indexOptions.containsKey("weights")) { + ops = ops.weights((org.bson.Document) indexOptions.get("weights")); + } - for (String key : indexOptions.keySet()) { - if (ObjectUtils.nullSafeEquals("2dsphere", indexOptions.get(key))) { - ops = ops.sphereVersion(2); - } + for (String key : indexOptions.keySet()) { + if (ObjectUtils.nullSafeEquals("2dsphere", indexOptions.get(key))) { + ops = ops.sphereVersion(2); } + } - if (indexOptions.containsKey("partialFilterExpression")) { - ops = ops.partialFilterExpression((org.bson.Document) indexOptions.get("partialFilterExpression")); - } + if (indexOptions.containsKey("partialFilterExpression")) { + ops = ops.partialFilterExpression((org.bson.Document) indexOptions.get("partialFilterExpression")); + } - if (indexOptions.containsKey("collation")) { - ops = ops.collation(fromDocument(indexOptions.get("collation", Document.class))); - } + if (indexOptions.containsKey("collation")) { + ops = ops.collation(fromDocument(indexOptions.get("collation", Document.class))); + } - if (indexOptions.containsKey("wildcardProjection")) { - ops.wildcardProjection(indexOptions.get("wildcardProjection", Document.class)); - } + if (indexOptions.containsKey("wildcardProjection")) { + ops.wildcardProjection(indexOptions.get("wildcardProjection", Document.class)); + } - if (indexOptions.containsKey("hidden")) { - ops = ops.hidden((Boolean) indexOptions.get("hidden")); - } + if (indexOptions.containsKey("hidden")) { + ops = ops.hidden((Boolean) indexOptions.get("hidden")); + } - return ops; - }; + return ops; } public static @Nullable Collation fromDocument(@Nullable Document source) { @@ -133,8 +113,4 @@ private static Converter getIndexDefinitionIndexO return org.springframework.data.mongodb.core.query.Collation.from(source).toMongoCollation(); } - private static Converter getDocumentIndexInfoConverter() { - return IndexInfo::indexInfoOf; - } - } From 8772742eddb58e005f4890c70aae14cbaf0b5109 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Tue, 10 Mar 2026 10:02:51 +0100 Subject: [PATCH 3/4] Revise index operations to remove redundant code. Refactored code duplications between DefaultIndexOperations and DefaultReactiveIndexOperations into IndexOperationsSupport. Added indexOps() overload to ReactiveMongoOperations accepting the entity class and collection name for consistency with MongoOperations. IndexOperationsSupport provides template methods for various mapping stages of index information. --- .../mongodb/core/DefaultIndexOperations.java | 171 ++++--------- .../core/DefaultIndexOperationsProvider.java | 4 +- .../core/DefaultReactiveIndexOperations.java | 111 +++------ .../mongodb/core/IndexOperationsSupport.java | 229 ++++++++++++++++++ .../data/mongodb/core/MongoOperations.java | 21 +- .../data/mongodb/core/MongoTemplate.java | 9 +- .../mongodb/core/ReactiveMongoOperations.java | 10 + .../mongodb/core/ReactiveMongoTemplate.java | 15 +- .../core/index/IndexOperationsProvider.java | 8 +- .../DefaultReactiveIndexOperationsTests.java | 22 +- .../core/ReactiveMongoTemplateIndexTests.java | 4 +- 11 files changed, 369 insertions(+), 235 deletions(-) create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/IndexOperationsSupport.java diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultIndexOperations.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultIndexOperations.java index 8924d4ad77..ab75b1dc89 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultIndexOperations.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultIndexOperations.java @@ -16,26 +16,18 @@ package org.springframework.data.mongodb.core; import java.util.ArrayList; -import java.util.Collection; import java.util.List; import org.bson.Document; import org.jspecify.annotations.Nullable; -import org.springframework.dao.DataAccessException; + import org.springframework.data.mongodb.MongoDatabaseFactory; -import org.springframework.data.mongodb.UncategorizedMongoDbException; import org.springframework.data.mongodb.core.convert.QueryMapper; import org.springframework.data.mongodb.core.index.IndexDefinition; import org.springframework.data.mongodb.core.index.IndexInfo; import org.springframework.data.mongodb.core.index.IndexOperations; -import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity; +import org.springframework.data.mongodb.core.mapping.CollectionName; import org.springframework.util.Assert; -import org.springframework.util.NumberUtils; - -import com.mongodb.MongoException; -import com.mongodb.client.MongoCollection; -import com.mongodb.client.MongoCursor; -import com.mongodb.client.model.IndexOptions; /** * Default implementation of {@link IndexOperations}. @@ -46,24 +38,18 @@ * @author Christoph Strobl * @author Mark Paluch */ -public class DefaultIndexOperations implements IndexOperations { - - private static final String PARTIAL_FILTER_EXPRESSION_KEY = "partialFilterExpression"; - - private final String collectionName; - private final QueryMapper mapper; - private final @Nullable Class type; +public class DefaultIndexOperations extends IndexOperationsSupport implements IndexOperations { private final MongoOperations mongoOperations; /** - * Creates a new {@link DefaultIndexOperations}. + * Creates a new {@code DefaultIndexOperations}. * * @param mongoDbFactory must not be {@literal null}. * @param collectionName must not be {@literal null}. * @param queryMapper must not be {@literal null}. * @deprecated since 2.1. Please use - * {@link DefaultIndexOperations#DefaultIndexOperations(MongoOperations, String, Class)}. + * {@code DefaultIndexOperations#DefaultIndexOperations(MongoOperations, String, Class)}. */ @Deprecated public DefaultIndexOperations(MongoDatabaseFactory mongoDbFactory, String collectionName, QueryMapper queryMapper) { @@ -71,7 +57,7 @@ public DefaultIndexOperations(MongoDatabaseFactory mongoDbFactory, String collec } /** - * Creates a new {@link DefaultIndexOperations}. + * Creates a new {@code DefaultIndexOperations}. * * @param mongoDbFactory must not be {@literal null}. * @param collectionName must not be {@literal null}. @@ -79,24 +65,16 @@ public DefaultIndexOperations(MongoDatabaseFactory mongoDbFactory, String collec * @param type Type used for mapping potential partial index filter expression. Can be {@literal null}. * @since 1.10 * @deprecated since 2.1. Please use - * {@link DefaultIndexOperations#DefaultIndexOperations(MongoOperations, String, Class)}. + * {@code DefaultIndexOperations#DefaultIndexOperations(MongoOperations, String, Class)}. */ @Deprecated public DefaultIndexOperations(MongoDatabaseFactory mongoDbFactory, String collectionName, QueryMapper queryMapper, @Nullable Class type) { - - Assert.notNull(mongoDbFactory, "MongoDbFactory must not be null"); - Assert.notNull(collectionName, "Collection name can not be null"); - Assert.notNull(queryMapper, "QueryMapper must not be null"); - - this.collectionName = collectionName; - this.mapper = queryMapper; - this.type = type; - this.mongoOperations = new MongoTemplate(mongoDbFactory); + this(new MongoTemplate(mongoDbFactory), collectionName, queryMapper, type); } /** - * Creates a new {@link DefaultIndexOperations}. + * Creates a new {@code DefaultIndexOperations}. * * @param mongoOperations must not be {@literal null}. * @param collectionName must not be {@literal null} or empty. @@ -104,51 +82,50 @@ public DefaultIndexOperations(MongoDatabaseFactory mongoDbFactory, String collec * @since 2.1 */ public DefaultIndexOperations(MongoOperations mongoOperations, String collectionName, @Nullable Class type) { + this(mongoOperations, CollectionName.just(collectionName), new QueryMapper(mongoOperations.getConverter()), type); + } - Assert.notNull(mongoOperations, "MongoOperations must not be null"); - Assert.hasText(collectionName, "Collection name must not be null or empty"); + /** + * Creates a new {@code DefaultIndexOperations}. + * + * @param mongoOperations must not be {@literal null}. + * @param collectionName must not be {@literal null}. + * @param queryMapper must not be {@literal null}. + * @param type used for mapping potential partial index filter expression. + * @since 5.1 + */ + public DefaultIndexOperations(MongoOperations mongoOperations, String collectionName, QueryMapper queryMapper, + @Nullable Class type) { + this(mongoOperations, CollectionName.just(collectionName), queryMapper, type); + } + + /** + * Creates a new {@code DefaultIndexOperations}. + * + * @param mongoOperations must not be {@literal null}. + * @param collectionName must not be {@literal null} or empty. + * @param type can be {@literal null}. + * @since 5.1 + */ + protected DefaultIndexOperations(MongoOperations mongoOperations, CollectionName collectionName, + QueryMapper queryMapper, @Nullable Class type) { + + super(collectionName, queryMapper, type); + Assert.notNull(mongoOperations, "MongoOperations must not be null"); this.mongoOperations = mongoOperations; - this.mapper = new QueryMapper(mongoOperations.getConverter()); - this.collectionName = collectionName; - this.type = type; } @Override - @SuppressWarnings("NullAway") public String createIndex(IndexDefinition indexDefinition) { return execute(collection -> { - MongoPersistentEntity entity = lookupPersistentEntity(type, collectionName); - - IndexOptions indexOptions = IndexConverters.toIndexOptions(indexDefinition); - - indexOptions = addPartialFilterIfPresent(indexOptions, indexDefinition.getIndexOptions(), entity); - indexOptions = addDefaultCollationIfRequired(indexOptions, entity); - - Document mappedKeys = mapper.getMappedSort(indexDefinition.getIndexKeys(), entity); - return collection.createIndex(mappedKeys, indexOptions); + CreateIndexCommand command = toCreateIndexCommand(indexDefinition); + return collection.createIndexes(command.toIndexModels(), command.createIndexOptions()).get(0); }); } - private @Nullable MongoPersistentEntity lookupPersistentEntity(@Nullable Class entityType, String collection) { - - if (entityType != null) { - return mapper.getMappingContext().getRequiredPersistentEntity(entityType); - } - - Collection> entities = mapper.getMappingContext().getPersistentEntities(); - - for (MongoPersistentEntity entity : entities) { - if (entity.getCollection().equals(collection)) { - return entity; - } - } - - return null; - } - @Override public void dropIndex(String name) { @@ -156,23 +133,16 @@ public void dropIndex(String name) { collection.dropIndex(name); return null; }); - } @Override @SuppressWarnings("NullAway") public void alterIndex(String name, org.springframework.data.mongodb.core.index.IndexOptions options) { - Document indexOptions = new Document("name", name); - indexOptions.putAll(options.toDocument()); - Document result = mongoOperations - .execute(db -> db.runCommand(new Document("collMod", collectionName).append("index", indexOptions))); + .execute(db -> db.runCommand(alterIndexCommand(name, options), Document.class)); - if (NumberUtils.convertNumberToTargetClass(result.get("ok", (Number) 0), Integer.class) != 1) { - throw new UncategorizedMongoDbException( - "Index '%s' could not be modified. Response was %s".formatted(name, result.toJson()), null); - } + validateAlterIndexResponse(name, result); } @Override @@ -181,62 +151,15 @@ public void dropAllIndexes() { } @Override - @SuppressWarnings("NullAway") public List getIndexInfo() { - - return execute(new CollectionCallback>() { - - public List doInCollection(MongoCollection collection) - throws MongoException, DataAccessException { - - MongoCursor cursor = collection.listIndexes(Document.class).iterator(); - return getIndexData(cursor); - } - - private List getIndexData(MongoCursor cursor) { - - int available = cursor.available(); - List indexInfoList = available > 0 ? new ArrayList<>(available) : new ArrayList<>(); - - while (cursor.hasNext()) { - - Document ix = cursor.next(); - IndexInfo indexInfo = IndexInfo.indexInfoOf(ix); - indexInfoList.add(indexInfo); - } - - return indexInfoList; - } - }); - } - - public @Nullable T execute(CollectionCallback callback) { - - Assert.notNull(callback, "CollectionCallback must not be null"); - - return mongoOperations.execute(collectionName, callback); - } - - private IndexOptions addPartialFilterIfPresent(IndexOptions ops, Document sourceOptions, - @Nullable MongoPersistentEntity entity) { - - if (!sourceOptions.containsKey(PARTIAL_FILTER_EXPRESSION_KEY)) { - return ops; - } - - Assert.isInstanceOf(Document.class, sourceOptions.get(PARTIAL_FILTER_EXPRESSION_KEY)); - return ops.partialFilterExpression( - mapper.getMappedSort((Document) sourceOptions.get(PARTIAL_FILTER_EXPRESSION_KEY), entity)); + return execute( + collection -> collection.listIndexes(Document.class).map(IndexInfo::indexInfoOf).into(new ArrayList<>())); } @SuppressWarnings("NullAway") - private static IndexOptions addDefaultCollationIfRequired(IndexOptions ops, - @Nullable MongoPersistentEntity entity) { - - if (ops.getCollation() != null || entity == null || !entity.hasCollation()) { - return ops; - } + public T execute(CollectionCallback callback) { - return ops.collation(entity.getCollation().toMongoCollation()); + Assert.notNull(callback, "CollectionCallback must not be null"); + return mongoOperations.execute(getCollectionName(), callback); } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultIndexOperationsProvider.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultIndexOperationsProvider.java index 0005f5e19f..7eb4cfdae9 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultIndexOperationsProvider.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultIndexOperationsProvider.java @@ -44,7 +44,7 @@ class DefaultIndexOperationsProvider implements IndexOperationsProvider { } @Override - public IndexOperations indexOps(String collectionName, @Nullable Class type) { - return new DefaultIndexOperations(mongoDbFactory, collectionName, mapper, type); + public IndexOperations indexOps(String collectionName, @Nullable Class entityClass) { + return new DefaultIndexOperations(mongoDbFactory, collectionName, mapper, entityClass); } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultReactiveIndexOperations.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultReactiveIndexOperations.java index 0e35a253d2..d7ce62dd33 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultReactiveIndexOperations.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/DefaultReactiveIndexOperations.java @@ -18,20 +18,15 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; -import java.util.Collection; - import org.bson.Document; import org.jspecify.annotations.Nullable; -import org.springframework.data.mongodb.UncategorizedMongoDbException; + import org.springframework.data.mongodb.core.convert.QueryMapper; import org.springframework.data.mongodb.core.index.IndexDefinition; import org.springframework.data.mongodb.core.index.IndexInfo; import org.springframework.data.mongodb.core.index.ReactiveIndexOperations; -import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity; +import org.springframework.data.mongodb.core.mapping.CollectionName; import org.springframework.util.Assert; -import org.springframework.util.NumberUtils; - -import com.mongodb.client.model.IndexOptions; /** * Default implementation of {@link ReactiveIndexOperations}. @@ -40,17 +35,12 @@ * @author Christoph Strobl * @since 2.0 */ -public class DefaultReactiveIndexOperations implements ReactiveIndexOperations { - - private static final String PARTIAL_FILTER_EXPRESSION_KEY = "partialFilterExpression"; +public class DefaultReactiveIndexOperations extends IndexOperationsSupport implements ReactiveIndexOperations { private final ReactiveMongoOperations mongoOperations; - private final String collectionName; - private final QueryMapper queryMapper; - private final @Nullable Class type; /** - * Creates a new {@link DefaultReactiveIndexOperations}. + * Creates a new {@code DefaultReactiveIndexOperations}. * * @param mongoOperations must not be {@literal null}. * @param collectionName must not be {@literal null}. @@ -62,41 +52,43 @@ public DefaultReactiveIndexOperations(ReactiveMongoOperations mongoOperations, S } /** - * Creates a new {@link DefaultReactiveIndexOperations}. + * Creates a new {@code DefaultReactiveIndexOperations}. * * @param mongoOperations must not be {@literal null}. * @param collectionName must not be {@literal null}. * @param queryMapper must not be {@literal null}. - * @param type used for mapping potential partial index filter expression, must not be {@literal null}. + * @param type used for mapping potential partial index filter expression. */ public DefaultReactiveIndexOperations(ReactiveMongoOperations mongoOperations, String collectionName, QueryMapper queryMapper, @Nullable Class type) { + this(mongoOperations, CollectionName.just(collectionName), queryMapper, type); + } - Assert.notNull(mongoOperations, "ReactiveMongoOperations must not be null"); - Assert.notNull(collectionName, "Collection must not be null"); - Assert.notNull(queryMapper, "QueryMapper must not be null"); + /** + * Creates a new {@code DefaultReactiveIndexOperations}. + * + * @param mongoOperations must not be {@literal null}. + * @param collectionName must not be {@literal null}. + * @param queryMapper must not be {@literal null}. + * @param type used for mapping potential partial index filter expression. + */ + protected DefaultReactiveIndexOperations(ReactiveMongoOperations mongoOperations, CollectionName collectionName, + QueryMapper queryMapper, @Nullable Class type) { + + super(collectionName, queryMapper, type); + Assert.notNull(mongoOperations, "ReactiveMongoOperations must not be null"); this.mongoOperations = mongoOperations; - this.collectionName = collectionName; - this.queryMapper = queryMapper; - this.type = type; } @Override @SuppressWarnings("NullAway") public Mono createIndex(IndexDefinition indexDefinition) { - return mongoOperations.execute(collectionName, collection -> { - - MongoPersistentEntity entity = getConfiguredEntity(); - - IndexOptions indexOptions = IndexConverters.toIndexOptions(indexDefinition); - - indexOptions = addPartialFilterIfPresent(indexOptions, indexDefinition.getIndexOptions(), entity); - indexOptions = addDefaultCollationIfRequired(indexOptions, entity); - - return collection.createIndex(indexDefinition.getIndexKeys(), indexOptions); + return mongoOperations.execute(getCollectionName(), collection -> { + CreateIndexCommand command = toCreateIndexCommand(indexDefinition); + return collection.createIndexes(command.toIndexModels(), command.createIndexOptions()); }).next(); } @@ -104,32 +96,16 @@ public Mono createIndex(IndexDefinition indexDefinition) { public Mono alterIndex(String name, org.springframework.data.mongodb.core.index.IndexOptions options) { return mongoOperations.execute(db -> { - Document indexOptions = new Document("name", name); - indexOptions.putAll(options.toDocument()); - - return Flux.from(db.runCommand(new Document("collMod", collectionName).append("index", indexOptions))) + return Flux.from(db.runCommand(alterIndexCommand(name, options))) .doOnNext(result -> { - if (NumberUtils.convertNumberToTargetClass(result.get("ok", (Number) 0), Integer.class) != 1) { - throw new UncategorizedMongoDbException( - "Index '%s' could not be modified. Response was %s".formatted(name, result.toJson()), null); - } + validateAlterIndexResponse(name, result); }); }).then(); } - private @Nullable MongoPersistentEntity lookupPersistentEntity(String collection) { - - Collection> entities = queryMapper.getMappingContext().getPersistentEntities(); - - return entities.stream() // - .filter(entity -> entity.getCollection().equals(collection)) // - .findFirst() // - .orElse(null); - } - @Override public Mono dropIndex(String name) { - return mongoOperations.execute(collectionName, collection -> collection.dropIndex(name)).then(); + return mongoOperations.execute(getCollectionName(), collection -> collection.dropIndex(name)).then(); } @Override @@ -139,39 +115,8 @@ public Mono dropAllIndexes() { @Override public Flux getIndexInfo() { - - return mongoOperations.execute(collectionName, collection -> collection.listIndexes(Document.class)) // + return mongoOperations.execute(getCollectionName(), collection -> collection.listIndexes(Document.class)) // .map(IndexInfo::indexInfoOf); } - private @Nullable MongoPersistentEntity getConfiguredEntity() { - - if (type != null) { - return queryMapper.getMappingContext().getRequiredPersistentEntity(type); - } - return lookupPersistentEntity(collectionName); - } - - private IndexOptions addPartialFilterIfPresent(IndexOptions ops, Document sourceOptions, - @Nullable MongoPersistentEntity entity) { - - if (!sourceOptions.containsKey(PARTIAL_FILTER_EXPRESSION_KEY)) { - return ops; - } - - Assert.isInstanceOf(Document.class, sourceOptions.get(PARTIAL_FILTER_EXPRESSION_KEY)); - return ops.partialFilterExpression( - queryMapper.getMappedObject((Document) sourceOptions.get(PARTIAL_FILTER_EXPRESSION_KEY), entity)); - } - - @SuppressWarnings("NullAway") - private static IndexOptions addDefaultCollationIfRequired(IndexOptions ops, - @Nullable MongoPersistentEntity entity) { - - if (ops.getCollation() != null || entity == null || !entity.hasCollation()) { - return ops; - } - - return ops.collation(entity.getCollation().toMongoCollation()); - } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/IndexOperationsSupport.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/IndexOperationsSupport.java new file mode 100644 index 0000000000..4d78ef224f --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/IndexOperationsSupport.java @@ -0,0 +1,229 @@ +/* + * Copyright 2026-present 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 + * + * https://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 java.util.List; + +import org.bson.Document; +import org.jspecify.annotations.Nullable; + +import org.springframework.data.mapping.context.MappingContext; +import org.springframework.data.mongodb.UncategorizedMongoDbException; +import org.springframework.data.mongodb.core.convert.QueryMapper; +import org.springframework.data.mongodb.core.index.IndexDefinition; +import org.springframework.data.mongodb.core.mapping.CollectionName; +import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity; +import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty; +import org.springframework.util.Assert; +import org.springframework.util.NumberUtils; + +import com.mongodb.client.model.CreateIndexOptions; +import com.mongodb.client.model.IndexModel; +import com.mongodb.client.model.IndexOptions; + +/** + * Base class for index operations. + *

+ * This class enables the implementations to use consistent commands and mapping to be implemented easily for the target + * execution model. + * + * @author Mark Paluch + * @since 5.1 + */ +public abstract class IndexOperationsSupport { + + private static final String PARTIAL_FILTER_EXPRESSION_KEY = "partialFilterExpression"; + + private final CollectionName collectionName; + private final QueryMapper queryMapper; + private final MappingContext, MongoPersistentProperty> mappingContext; + private final @Nullable Class entityType; + + /** + * Creates a new {@code IndexOperationsSupport} for the given {@link CollectionName} and {@link QueryMapper}. + * + * @param collectionName the collection name to use. + * @param queryMapper the query mapper to use for mapping index keys. + * @param entityType optional entity entityType to use for mapping. + */ + public IndexOperationsSupport(CollectionName collectionName, QueryMapper queryMapper, @Nullable Class entityType) { + + Assert.notNull(collectionName, "Collection must not be null"); + Assert.notNull(queryMapper, "QueryMapper must not be null"); + + this.collectionName = collectionName; + this.queryMapper = queryMapper; + this.mappingContext = queryMapper.getMappingContext(); + this.entityType = entityType; + } + + /** + * Map {@link IndexDefinition} to {@link CreateIndexCommand}. + * + * @param indexDefinition the index definition to map. + * @return the mapped {@link CreateIndexCommand}. + */ + protected CreateIndexCommand toCreateIndexCommand(IndexDefinition indexDefinition) { + + IndexOptions indexOptions = mapIndexDefinition(indexDefinition); + Document keys = mapKeys(indexDefinition); + + return new DefaultCreateIndexCommand(keys, indexOptions, new CreateIndexOptions()); + } + + /** + * Map {@link IndexDefinition} to {@link IndexOptions}. + * + * @param indexDefinition the index definition to map. + * @return the mapped {@link IndexOptions}. + */ + protected IndexOptions mapIndexDefinition(IndexDefinition indexDefinition) { + + MongoPersistentEntity entity = getConfiguredEntity(); + + IndexOptions indexOptions = IndexConverters.toIndexOptions(indexDefinition); + + indexOptions = addPartialFilterIfPresent(indexOptions, indexDefinition.getIndexOptions(), entity); + indexOptions = addDefaultCollationIfRequired(indexOptions, entity); + + return indexOptions; + } + + /** + * Map index keys to {@link Document}. + * + * @param indexDefinition the source index definition. + * @return the mapped {@link Document}. + */ + protected Document mapKeys(IndexDefinition indexDefinition) { + return queryMapper.getMappedSort(indexDefinition.getIndexKeys(), getConfiguredEntity()); + } + + /** + * Return the collection name to use. + */ + protected String getCollectionName() { + return collectionName.getCollectionName(mappingContext::getRequiredPersistentEntity); + } + + /** + * Create the command to alter an index. + * + * @param name index name. + * @param options index options to use. + * @return + */ + Document alterIndexCommand(String name, org.springframework.data.mongodb.core.index.IndexOptions options) { + Document indexOptions = new Document("name", name); + indexOptions.putAll(options.toDocument()); + return new Document("collMod", getCollectionName()).append("index", indexOptions); + } + + /** + * Validate the alter index response and throw {@link UncategorizedMongoDbException} if the index could not be + * altered. + * + * @param name index name. + * @param result result document from an earlier + * {@link #alterIndexCommand(String, org.springframework.data.mongodb.core.index.IndexOptions)}. + */ + static void validateAlterIndexResponse(String name, Document result) { + Integer ok = NumberUtils.convertNumberToTargetClass(result.get("ok", (Number) 0), Integer.class); + if (ok != 1) { + throw new UncategorizedMongoDbException( + "Index '%s' could not be modified. Response was %s".formatted(name, result.toJson()), null); + } + } + + private @Nullable MongoPersistentEntity getConfiguredEntity() { + return entityType != null ? mappingContext.getRequiredPersistentEntity(entityType) : null; + } + + private IndexOptions addPartialFilterIfPresent(IndexOptions ops, Document sourceOptions, + @Nullable MongoPersistentEntity entity) { + + if (!sourceOptions.containsKey(PARTIAL_FILTER_EXPRESSION_KEY)) { + return ops; + } + + Assert.isInstanceOf(Document.class, sourceOptions.get(PARTIAL_FILTER_EXPRESSION_KEY)); + return ops.partialFilterExpression( + queryMapper.getMappedObject((Document) sourceOptions.get(PARTIAL_FILTER_EXPRESSION_KEY), entity)); + } + + @SuppressWarnings("NullAway") + private static IndexOptions addDefaultCollationIfRequired(IndexOptions ops, + @Nullable MongoPersistentEntity entity) { + + if (ops.getCollation() != null || entity == null || !entity.hasCollation()) { + return ops; + } + + return ops.collation(entity.getCollation().toMongoCollation()); + } + + /** + * Interface for a create index command. + */ + protected interface CreateIndexCommand { + + /** + * Return the index keys. + */ + Document keys(); + + /** + * Return the index options. + */ + IndexOptions indexOptions(); + + /** + * Return the index creation options. + */ + CreateIndexOptions createIndexOptions(); + + /** + * Create a new {@code CreateIndexCommand} with the given {@link CreateIndexOptions}. + * + * @param createIndexOptions the new {@link CreateIndexOptions} to use. + * @return a new {@code CreateIndexCommand} with the given {@link CreateIndexOptions} applied. + */ + default CreateIndexCommand withCreateIndexOptions(CreateIndexOptions createIndexOptions) { + return new DefaultCreateIndexCommand(keys(), indexOptions(), createIndexOptions); + } + + default IndexModel toIndexModel() { + return new IndexModel(keys(), indexOptions()); + } + + default List toIndexModels() { + return List.of(toIndexModel()); + } + } + + /** + * Value object for a create index command. + * + * @param keys index keys. + * @param indexOptions the index options to use. + * @param createIndexOptions index creation options. + */ + private record DefaultCreateIndexCommand(Document keys, IndexOptions indexOptions, + CreateIndexOptions createIndexOptions) implements CreateIndexCommand { + + } + +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoOperations.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoOperations.java index ee4f2e3ee4..8447ec33f1 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoOperations.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoOperations.java @@ -26,6 +26,7 @@ import org.bson.Document; import org.jspecify.annotations.Nullable; + import org.springframework.data.domain.KeysetScrollPosition; import org.springframework.data.domain.Sort; import org.springframework.data.domain.Window; @@ -39,8 +40,8 @@ import org.springframework.data.mongodb.core.aggregation.AggregationUpdate; import org.springframework.data.mongodb.core.aggregation.TypedAggregation; import org.springframework.data.mongodb.core.bulk.Bulk; -import org.springframework.data.mongodb.core.bulk.BulkWriteResult; import org.springframework.data.mongodb.core.bulk.BulkWriteOptions; +import org.springframework.data.mongodb.core.bulk.BulkWriteResult; import org.springframework.data.mongodb.core.convert.MappingMongoConverter; import org.springframework.data.mongodb.core.convert.MongoConverter; import org.springframework.data.mongodb.core.index.IndexOperations; @@ -450,16 +451,26 @@ MongoCollection createView(String name, String source, AggregationPipe void dropCollection(String collectionName); /** - * Returns the operations that can be performed on indexes + * Returns the operations that can be performed on indexes. * - * @return index operations on the named collection + * @return index operations on the named collection. */ IndexOperations indexOps(String collectionName); /** - * Returns the operations that can be performed on indexes + * Returns the operations that can be performed on indexes. + * + * @param collectionName name of the MongoDB collection, must not be {@literal null}. + * @param entityClass the entityClass used for field mapping. + * @return index operations on the named collection. + * @since 5.1 + */ + IndexOperations indexOps(String collectionName, Class entityClass); + + /** + * Returns the operations that can be performed on indexes. * - * @return index operations on the named collection associated with the given entity class + * @return index operations on the named collection associated with the given entity class. */ IndexOperations indexOps(Class entityClass); diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java index 7b1a5181e4..45c8b0e899 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java @@ -821,8 +821,8 @@ public IndexOperations indexOps(String collectionName) { } @Override - public IndexOperations indexOps(String collectionName, @Nullable Class type) { - return new DefaultIndexOperations(this, collectionName, type); + public IndexOperations indexOps(String collectionName, @Nullable Class entityClass) { + return createIndexOperations(collectionName, this.queryMapper, entityClass); } @Override @@ -830,6 +830,11 @@ public IndexOperations indexOps(Class entityClass) { return indexOps(getCollectionName(entityClass), entityClass); } + protected IndexOperations createIndexOperations(String collectionName, QueryMapper queryMapper, + @Nullable Class entityClass) { + return new DefaultIndexOperations(this, collectionName, queryMapper, entityClass); + } + @Override public SearchIndexOperations searchIndexOps(String collectionName) { return searchIndexOps(null, collectionName); diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoOperations.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoOperations.java index 69d4741bf4..808c38f0ed 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoOperations.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoOperations.java @@ -92,6 +92,16 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations { */ ReactiveIndexOperations indexOps(String collectionName); + /** + * Returns the reactive operations that can be performed on indexes + * + * @param collectionName name of the MongoDB collection, must not be {@literal null}. + * @param entityClass the entityClass used for field mapping. + * @return index operations on the named collection. + * @since 5.1 + */ + ReactiveIndexOperations indexOps(String collectionName, Class entityClass); + /** * Returns the reactive operations that can be performed on indexes * diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java index 8c879a192d..c1376b0cda 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java @@ -510,14 +510,25 @@ QueryOperations getQueryOperations() { return queryOperations; } + @Override public ReactiveIndexOperations indexOps(String collectionName) { - return new DefaultReactiveIndexOperations(this, collectionName, this.queryMapper); + return createIndexOperations(collectionName, this.queryMapper, null); + } + + @Override + public ReactiveIndexOperations indexOps(String collectionName, @Nullable Class entityClass) { + return createIndexOperations(collectionName, this.queryMapper, entityClass); } @Override public ReactiveIndexOperations indexOps(Class entityClass) { - return new DefaultReactiveIndexOperations(this, getCollectionName(entityClass), this.queryMapper, entityClass); + return indexOps(getCollectionName(entityClass), entityClass); + } + + protected ReactiveIndexOperations createIndexOperations(String collectionName, QueryMapper queryMapper, + @Nullable Class entityClass) { + return new DefaultReactiveIndexOperations(this, collectionName, queryMapper, entityClass); } @Override diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexOperationsProvider.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexOperationsProvider.java index 451d88e44a..2d80ba0050 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexOperationsProvider.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexOperationsProvider.java @@ -31,7 +31,7 @@ public interface IndexOperationsProvider { * Returns the operations that can be performed on indexes. * * @param collectionName name of the MongoDB collection, must not be {@literal null}. - * @return index operations on the named collection + * @return index operations on the named collection. */ default IndexOperations indexOps(String collectionName) { return indexOps(collectionName, null); @@ -41,10 +41,10 @@ default IndexOperations indexOps(String collectionName) { * Returns the operations that can be performed on indexes. * * @param collectionName name of the MongoDB collection, must not be {@literal null}. - * @param type the type used for field mapping. Can be {@literal null}. - * @return index operations on the named collection + * @param entityClass the entityClass used for field mapping. Can be {@literal null}. + * @return index operations on the named collection. * @since 3.2 */ - IndexOperations indexOps(String collectionName, @Nullable Class type); + IndexOperations indexOps(String collectionName, @Nullable Class entityClass); } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultReactiveIndexOperationsTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultReactiveIndexOperationsTests.java index f1d3bf412e..4710d94239 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultReactiveIndexOperationsTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultReactiveIndexOperationsTests.java @@ -42,6 +42,8 @@ import com.mongodb.reactivestreams.client.MongoCollection; /** + * Integration tests for {@link DefaultReactiveIndexOperations}. + * * @author Christoph Strobl * @author Mark Paluch * @author Mathieu Ouellet @@ -49,22 +51,22 @@ public class DefaultReactiveIndexOperationsTests { @Template(initialEntitySet = DefaultIndexOperationsIntegrationTestsSample.class) // - static ReactiveMongoTestTemplate template; + private static ReactiveMongoTestTemplate template; - String collectionName = template.getCollectionName(DefaultIndexOperationsIntegrationTestsSample.class); + private String collectionName = template.getCollectionName(DefaultIndexOperationsIntegrationTestsSample.class); - DefaultReactiveIndexOperations indexOps = new DefaultReactiveIndexOperations(template, collectionName, - new QueryMapper(template.getConverter())); + private DefaultReactiveIndexOperations indexOps = new DefaultReactiveIndexOperations(template, collectionName, + new QueryMapper(template.getConverter()), DefaultIndexOperationsIntegrationTestsSample.class); @BeforeEach - public void setUp() { + void setUp() { template.getCollection(collectionName).flatMapMany(MongoCollection::dropIndexes) // .as(StepVerifier::create) // .verifyComplete(); } @Test // DATAMONGO-1518 - public void shouldCreateIndexWithCollationCorrectly() { + void shouldCreateIndexWithCollationCorrectly() { IndexDefinition id = new Index().named("with-collation").on("xyz", Direction.ASC) .collation(Collation.of("de_AT").caseFirst(CaseFirst.off())); @@ -96,7 +98,7 @@ public void shouldCreateIndexWithCollationCorrectly() { } @Test // DATAMONGO-1682, DATAMONGO-2198 - public void shouldApplyPartialFilterCorrectly() { + void shouldApplyPartialFilterCorrectly() { IndexDefinition id = new Index().named("partial-with-criteria").on("k3y", Direction.ASC) .partial(of(where("q-t-y").gte(10))); @@ -112,7 +114,7 @@ public void shouldApplyPartialFilterCorrectly() { } @Test // DATAMONGO-1682, DATAMONGO-2198 - public void shouldApplyPartialFilterWithMappedPropertyCorrectly() { + void shouldApplyPartialFilterWithMappedPropertyCorrectly() { IndexDefinition id = new Index().named("partial-with-mapped-criteria").on("k3y", Direction.ASC) .partial(of(where("quantity").gte(10))); @@ -127,7 +129,7 @@ public void shouldApplyPartialFilterWithMappedPropertyCorrectly() { } @Test // DATAMONGO-1682, DATAMONGO-2198 - public void shouldApplyPartialDBOFilterCorrectly() { + void shouldApplyPartialDBOFilterCorrectly() { IndexDefinition id = new Index().named("partial-with-dbo").on("k3y", Direction.ASC) .partial(of(new org.bson.Document("qty", new org.bson.Document("$gte", 10)))); @@ -144,7 +146,7 @@ public void shouldApplyPartialDBOFilterCorrectly() { } @Test // DATAMONGO-1682, DATAMONGO-2198 - public void shouldFavorExplicitMappingHintViaClass() { + void shouldFavorExplicitMappingHintViaClass() { IndexDefinition id = new Index().named("partial-with-inheritance").on("k3y", Direction.ASC) .partial(of(where("age").gte(10))); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveMongoTemplateIndexTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveMongoTemplateIndexTests.java index 491ebd2cd2..40225d53b3 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveMongoTemplateIndexTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveMongoTemplateIndexTests.java @@ -15,12 +15,9 @@ */ package org.springframework.data.mongodb.core; -import static org.assertj.core.data.Index.*; import static org.assertj.core.data.Index.atIndex; import static org.springframework.data.mongodb.test.util.Assertions.*; -import org.junit.jupiter.api.RepeatedTest; -import org.junitpioneer.jupiter.RetryingTest; import reactor.core.publisher.Flux; import reactor.test.StepVerifier; @@ -32,6 +29,7 @@ import org.bson.Document; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junitpioneer.jupiter.RetryingTest; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.data.annotation.Id; From 92c19b30d27fea031ab676361ff1180b39619f4d Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Tue, 10 Mar 2026 10:52:00 +0100 Subject: [PATCH 4/4] =?UTF-8?q?=F0=9F=99=84Tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/DefaultIndexOperationsUnitTests.java | 42 ++++++++------ ...faultReactiveIndexOperationsUnitTests.java | 40 ++++++++----- ...PersistentEntityIndexCreatorUnitTests.java | 57 +++++++++++-------- ...PersistentEntityIndexCreatorUnitTests.java | 21 +++++-- 4 files changed, 99 insertions(+), 61 deletions(-) diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultIndexOperationsUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultIndexOperationsUnitTests.java index 9e66e259b9..298d9bb31f 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultIndexOperationsUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultIndexOperationsUnitTests.java @@ -18,13 +18,18 @@ import static org.assertj.core.api.Assertions.*; import static org.mockito.Mockito.*; +import java.util.List; +import java.util.function.BiConsumer; + import org.bson.Document; +import org.bson.conversions.Bson; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; + import org.springframework.data.domain.Sort.Direction; import org.springframework.data.mongodb.MongoDatabaseFactory; import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver; @@ -37,6 +42,8 @@ import com.mongodb.client.MongoCollection; import com.mongodb.client.MongoDatabase; +import com.mongodb.client.model.CreateIndexOptions; +import com.mongodb.client.model.IndexModel; import com.mongodb.client.model.IndexOptions; /** @@ -64,7 +71,7 @@ void setUp() { when(factory.getMongoDatabase()).thenReturn(db); when(factory.getExceptionTranslator()).thenReturn(exceptionTranslator); when(db.getCollection(any(), any(Class.class))).thenReturn(collection); - when(collection.createIndex(any(), any(IndexOptions.class))).thenReturn("OK"); + when(collection.createIndexes(anyList(), any(CreateIndexOptions.class))).thenReturn(List.of("OK")); this.mappingContext = new MongoMappingContext(); this.converter = spy(new MappingMongoConverter(new DefaultDbRefResolver(factory), mappingContext)); @@ -76,7 +83,7 @@ void indexOperationsMapFieldNameCorrectly() { indexOpsFor(Jedi.class).ensureIndex(new Index("name", Direction.DESC)); - verify(collection).createIndex(eq(new Document("firstname", -1)), any()); + verifyCreateIndex((keys, options) -> assertThat(keys).isEqualTo(new Document("firstname", -1))); } @Test // DATAMONGO-1854 @@ -84,10 +91,7 @@ void ensureIndexDoesNotSetCollectionIfNoDefaultDefined() { indexOpsFor(Jedi.class).ensureIndex(new Index("firstname", Direction.DESC)); - ArgumentCaptor options = ArgumentCaptor.forClass(IndexOptions.class); - verify(collection).createIndex(any(), options.capture()); - - assertThat(options.getValue().getCollation()).isNull(); + verifyCreateIndex((keys, options) -> assertThat(options.getCollation()).isNull()); } @Test // DATAMONGO-1854 @@ -95,11 +99,8 @@ void ensureIndexUsesDefaultCollationIfNoneDefinedInOptions() { indexOpsFor(Sith.class).ensureIndex(new Index("firstname", Direction.DESC)); - ArgumentCaptor options = ArgumentCaptor.forClass(IndexOptions.class); - verify(collection).createIndex(any(), options.capture()); - - assertThat(options.getValue().getCollation()) - .isEqualTo(com.mongodb.client.model.Collation.builder().locale("de_AT").build()); + verifyCreateIndex((keys, options) -> assertThat(options.getCollation()) + .isEqualTo(com.mongodb.client.model.Collation.builder().locale("de_AT").build())); } @Test // DATAMONGO-1854 @@ -107,11 +108,8 @@ void ensureIndexDoesNotUseDefaultCollationIfExplicitlySpecifiedInTheIndex() { indexOpsFor(Sith.class).ensureIndex(new Index("firstname", Direction.DESC).collation(Collation.of("en_US"))); - ArgumentCaptor options = ArgumentCaptor.forClass(IndexOptions.class); - verify(collection).createIndex(any(), options.capture()); - - assertThat(options.getValue().getCollation()) - .isEqualTo(com.mongodb.client.model.Collation.builder().locale("en_US").build()); + verifyCreateIndex((keys, options) -> assertThat(options.getCollation()) + .isEqualTo(com.mongodb.client.model.Collation.builder().locale("en_US").build())); } @Test // DATAMONGO-1183 @@ -119,7 +117,7 @@ void shouldCreateHashedIndexCorrectly() { indexOpsFor(Jedi.class).ensureIndex(HashedIndex.hashed("name")); - verify(collection).createIndex(eq(new Document("firstname", "hashed")), any()); + verifyCreateIndex((keys, options) -> assertThat(keys).isEqualTo(new Document("firstname", "hashed"))); } @Test // GH-4698 @@ -131,6 +129,16 @@ void shouldConsiderGivenCollectionName() { verify(db).getCollection(eq("foo"), any(Class.class)); } + private void verifyCreateIndex(BiConsumer consumer) { + + ArgumentCaptor> captor = ArgumentCaptor.forClass(List.class); + + verify(collection).createIndexes(captor.capture(), any()); + + IndexModel indexModel = captor.getValue().get(0); + consumer.accept(indexModel.getKeys(), indexModel.getOptions()); + } + private DefaultIndexOperations indexOpsFor(Class type) { return new DefaultIndexOperations(template, template.getCollectionName(type), type); } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultReactiveIndexOperationsUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultReactiveIndexOperationsUnitTests.java index 195952dbec..a866ae3ede 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultReactiveIndexOperationsUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/DefaultReactiveIndexOperationsUnitTests.java @@ -20,7 +20,11 @@ import reactor.core.publisher.Mono; +import java.util.List; +import java.util.function.BiConsumer; + import org.bson.Document; +import org.bson.conversions.Bson; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -28,6 +32,7 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.reactivestreams.Publisher; + import org.springframework.data.domain.Sort.Direction; import org.springframework.data.mongodb.ReactiveMongoDatabaseFactory; import org.springframework.data.mongodb.core.convert.MappingMongoConverter; @@ -38,13 +43,18 @@ import org.springframework.data.mongodb.core.mapping.MongoMappingContext; import org.springframework.data.mongodb.core.query.Collation; +import com.mongodb.client.model.CreateIndexOptions; +import com.mongodb.client.model.IndexModel; import com.mongodb.client.model.IndexOptions; import com.mongodb.reactivestreams.client.MongoCollection; import com.mongodb.reactivestreams.client.MongoDatabase; /** + * Unit tests for {@link DefaultReactiveIndexOperations}. + * * @author Christoph Strobl * @author Mathieu Ouellet + * @author Mark Paluch */ @ExtendWith(MockitoExtension.class) public class DefaultReactiveIndexOperationsUnitTests { @@ -66,7 +76,7 @@ void setUp() { when(factory.getMongoDatabase()).thenReturn(Mono.just(db)); when(factory.getExceptionTranslator()).thenReturn(exceptionTranslator); when(db.getCollection(any(), any(Class.class))).thenReturn(collection); - when(collection.createIndex(any(), any(IndexOptions.class))).thenReturn(publisher); + when(collection.createIndexes(anyList(), any(CreateIndexOptions.class))).thenReturn(publisher); this.mappingContext = new MongoMappingContext(); this.converter = spy(new MappingMongoConverter(NoOpDbRefResolver.INSTANCE, mappingContext)); @@ -78,10 +88,7 @@ void ensureIndexDoesNotSetCollectionIfNoDefaultDefined() { indexOpsFor(Jedi.class).ensureIndex(new Index("firstname", Direction.DESC)).subscribe(); - ArgumentCaptor options = ArgumentCaptor.forClass(IndexOptions.class); - verify(collection).createIndex(any(), options.capture()); - - assertThat(options.getValue().getCollation()).isNull(); + verifyCreateIndex((keys, options) -> assertThat(options.getCollation()).isNull()); } @Test // DATAMONGO-1854 @@ -89,11 +96,8 @@ void ensureIndexUsesDefaultCollationIfNoneDefinedInOptions() { indexOpsFor(Sith.class).ensureIndex(new Index("firstname", Direction.DESC)).subscribe(); - ArgumentCaptor options = ArgumentCaptor.forClass(IndexOptions.class); - verify(collection).createIndex(any(), options.capture()); - - assertThat(options.getValue().getCollation()) - .isEqualTo(com.mongodb.client.model.Collation.builder().locale("de_AT").build()); + verifyCreateIndex((keys, options) -> assertThat(options.getCollation()) + .isEqualTo(com.mongodb.client.model.Collation.builder().locale("de_AT").build())); } @Test // DATAMONGO-1854 @@ -102,11 +106,19 @@ void ensureIndexDoesNotUseDefaultCollationIfExplicitlySpecifiedInTheIndex() { indexOpsFor(Sith.class).ensureIndex(new Index("firstname", Direction.DESC).collation(Collation.of("en_US"))) .subscribe(); - ArgumentCaptor options = ArgumentCaptor.forClass(IndexOptions.class); - verify(collection).createIndex(any(), options.capture()); - assertThat(options.getValue().getCollation()) - .isEqualTo(com.mongodb.client.model.Collation.builder().locale("en_US").build()); + verifyCreateIndex((keys, options) -> assertThat(options.getCollation()) + .isEqualTo(com.mongodb.client.model.Collation.builder().locale("en_US").build())); + } + + private void verifyCreateIndex(BiConsumer consumer) { + + ArgumentCaptor> captor = ArgumentCaptor.forClass(List.class); + + verify(collection).createIndexes(captor.capture(), any()); + + IndexModel indexModel = captor.getValue().get(0); + consumer.accept(indexModel.getKeys(), indexModel.getOptions()); } private DefaultReactiveIndexOperations indexOpsFor(Class type) { diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexCreatorUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexCreatorUnitTests.java index 71837d7803..e3b76afdc8 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexCreatorUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/MongoPersistentEntityIndexCreatorUnitTests.java @@ -20,6 +20,7 @@ import java.util.Collections; import java.util.Date; +import java.util.List; import java.util.concurrent.TimeUnit; import org.junit.jupiter.api.BeforeEach; @@ -46,6 +47,8 @@ import com.mongodb.MongoException; import com.mongodb.client.MongoCollection; import com.mongodb.client.MongoDatabase; +import com.mongodb.client.model.CreateIndexOptions; +import com.mongodb.client.model.IndexModel; import com.mongodb.client.model.IndexOptions; /** @@ -67,16 +70,14 @@ public class MongoPersistentEntityIndexCreatorUnitTests { private @Mock MongoCollection collection; private MongoTemplate mongoTemplate; - private ArgumentCaptor keysCaptor; - private ArgumentCaptor optionsCaptor; + private ArgumentCaptor> indexModelCaptor; private ArgumentCaptor collectionCaptor; @BeforeEach void setUp() { - keysCaptor = ArgumentCaptor.forClass(org.bson.Document.class); - optionsCaptor = ArgumentCaptor.forClass(IndexOptions.class); collectionCaptor = ArgumentCaptor.forClass(String.class); + indexModelCaptor = ArgumentCaptor.forClass(List.class); when(factory.getMongoDatabase()).thenReturn(db); when(factory.getExceptionTranslator()).thenReturn(new MongoExceptionTranslator()); @@ -84,8 +85,7 @@ void setUp() { .thenReturn((MongoCollection) collection); mongoTemplate = new MongoTemplate(factory); - - when(collection.createIndex(keysCaptor.capture(), optionsCaptor.capture())).thenReturn("OK"); + when(collection.createIndexes(indexModelCaptor.capture(), any(CreateIndexOptions.class))).thenReturn(List.of("OK")); } @Test @@ -95,10 +95,12 @@ void buildsIndexDefinitionUsingFieldName() { new MongoPersistentEntityIndexCreator(mappingContext, mongoTemplate); - assertThat(keysCaptor.getValue()).isNotNull().containsKey("fieldname"); - assertThat(optionsCaptor.getValue().getName()).isEqualTo("indexName"); - assertThat(optionsCaptor.getValue().isBackground()).isFalse(); - assertThat(optionsCaptor.getValue().getExpireAfter(TimeUnit.SECONDS)).isNull(); + IndexModel indexModel = indexModelCaptor.getValue().get(0); + + assertThat(indexModel.getKeys().toBsonDocument()).isNotNull().containsKey("fieldname"); + assertThat(indexModel.getOptions().getName()).isEqualTo("indexName"); + assertThat(indexModel.getOptions().isBackground()).isFalse(); + assertThat(indexModel.getOptions().getExpireAfter(TimeUnit.SECONDS)).isNull(); } @Test @@ -135,10 +137,12 @@ void triggersBackgroundIndexingIfConfigured() { MongoMappingContext mappingContext = prepareMappingContext(AnotherPerson.class); new MongoPersistentEntityIndexCreator(mappingContext, mongoTemplate); - assertThat(keysCaptor.getValue()).isNotNull().containsKey("lastname"); - assertThat(optionsCaptor.getValue().getName()).isEqualTo("lastname"); - assertThat(optionsCaptor.getValue().isBackground()).isTrue(); - assertThat(optionsCaptor.getValue().getExpireAfter(TimeUnit.SECONDS)).isNull(); + IndexModel indexModel = indexModelCaptor.getValue().get(0); + + assertThat(indexModel.getKeys().toBsonDocument()).isNotNull().containsKey("lastname"); + assertThat(indexModel.getOptions().getName()).isEqualTo("lastname"); + assertThat(indexModel.getOptions().isBackground()).isTrue(); + assertThat(indexModel.getOptions().getExpireAfter(TimeUnit.SECONDS)).isNull(); } @Test // DATAMONGO-544 @@ -147,8 +151,10 @@ void expireAfterSecondsIfConfigured() { MongoMappingContext mappingContext = prepareMappingContext(Milk.class); new MongoPersistentEntityIndexCreator(mappingContext, mongoTemplate); - assertThat(keysCaptor.getValue()).isNotNull().containsKey("expiry"); - assertThat(optionsCaptor.getValue().getExpireAfter(TimeUnit.SECONDS)).isEqualTo(60); + IndexModel indexModel = indexModelCaptor.getValue().get(0); + + assertThat(indexModel.getKeys().toBsonDocument()).isNotNull().containsKey("expiry"); + assertThat(indexModel.getOptions().getExpireAfter(TimeUnit.SECONDS)).isEqualTo(60); } @Test // DATAMONGO-899 @@ -157,9 +163,11 @@ void createsNotNestedGeoSpatialIndexCorrectly() { MongoMappingContext mappingContext = prepareMappingContext(Wrapper.class); new MongoPersistentEntityIndexCreator(mappingContext, mongoTemplate); - assertThat(keysCaptor.getValue()).isEqualTo(new org.bson.Document("company.address.location", "2d")); + IndexModel indexModel = indexModelCaptor.getValue().get(0); + + assertThat(indexModel.getKeys()).isEqualTo(new org.bson.Document("company.address.location", "2d")); - IndexOptions opts = optionsCaptor.getValue(); + IndexOptions opts = indexModel.getOptions(); assertThat(opts.getName()).isEqualTo("company.address.location"); assertThat(opts.getMin()).isCloseTo(-180d, offset(0d)); assertThat(opts.getMax()).isCloseTo(180d, offset(0d)); @@ -172,8 +180,10 @@ void autoGeneratedIndexNameShouldGenerateNoName() { MongoMappingContext mappingContext = prepareMappingContext(EntityWithGeneratedIndexName.class); new MongoPersistentEntityIndexCreator(mappingContext, mongoTemplate); - assertThat(keysCaptor.getValue()).doesNotContainKey("name").containsKey("lastname"); - assertThat(optionsCaptor.getValue().getName()).isNull(); + IndexModel indexModel = indexModelCaptor.getValue().get(0); + + assertThat(indexModel.getKeys().toBsonDocument()).doesNotContainKey("name").containsKey("lastname"); + assertThat(indexModel.getOptions().getName()).isNull(); } @Test // DATAMONGO-367 @@ -203,8 +213,8 @@ void indexCreationShouldNotCreateNewCollectionForNestedIndexStructures() { @Test // DATAMONGO-1125 void createIndexShouldUsePersistenceExceptionTranslatorForNonDataIntegrityConcerns() { - doThrow(new MongoException(6, "HostUnreachable")).when(collection).createIndex(any(org.bson.Document.class), - any(IndexOptions.class)); + doThrow(new MongoException(6, "HostUnreachable")).when(collection).createIndexes(anyList(), + any(CreateIndexOptions.class)); MongoMappingContext mappingContext = prepareMappingContext(Person.class); @@ -215,8 +225,7 @@ void createIndexShouldUsePersistenceExceptionTranslatorForNonDataIntegrityConcer @Test // DATAMONGO-1125 void createIndexShouldNotConvertUnknownExceptionTypes() { - doThrow(new ClassCastException("o_O")).when(collection).createIndex(any(org.bson.Document.class), - any(IndexOptions.class)); + doThrow(new ClassCastException("o_O")).when(collection).createIndexes(anyList(), any(CreateIndexOptions.class)); MongoMappingContext mappingContext = prepareMappingContext(Person.class); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/ReactiveMongoPersistentEntityIndexCreatorUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/ReactiveMongoPersistentEntityIndexCreatorUnitTests.java index 3ff07ecf7c..eb8a9d4891 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/ReactiveMongoPersistentEntityIndexCreatorUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/ReactiveMongoPersistentEntityIndexCreatorUnitTests.java @@ -22,6 +22,7 @@ import reactor.test.StepVerifier; import java.util.Collections; +import java.util.List; import java.util.concurrent.TimeUnit; import org.junit.jupiter.api.BeforeEach; @@ -42,6 +43,8 @@ import org.springframework.data.mongodb.core.mapping.MongoMappingContext; import com.mongodb.MongoException; +import com.mongodb.client.model.CreateIndexOptions; +import com.mongodb.client.model.IndexModel; import com.mongodb.client.model.IndexOptions; import com.mongodb.reactivestreams.client.MongoCollection; import com.mongodb.reactivestreams.client.MongoDatabase; @@ -62,6 +65,7 @@ public class ReactiveMongoPersistentEntityIndexCreatorUnitTests { @Mock MongoDatabase db; @Mock MongoCollection collection; + private ArgumentCaptor> indexModelCaptor; private ArgumentCaptor keysCaptor; private ArgumentCaptor optionsCaptor; private ArgumentCaptor collectionCaptor; @@ -79,8 +83,11 @@ void setUp() { keysCaptor = ArgumentCaptor.forClass(org.bson.Document.class); optionsCaptor = ArgumentCaptor.forClass(IndexOptions.class); collectionCaptor = ArgumentCaptor.forClass(String.class); + indexModelCaptor = ArgumentCaptor.forClass(List.class); when(collection.createIndex(keysCaptor.capture(), optionsCaptor.capture())).thenReturn(Mono.just("OK")); + when(collection.createIndexes(indexModelCaptor.capture(), any(CreateIndexOptions.class))) + .thenReturn(Mono.just("OK")); } @Test // DATAMONGO-1928 @@ -94,16 +101,18 @@ void buildsIndexDefinitionUsingFieldName() { publisher.as(StepVerifier::create).verifyComplete(); - assertThat(keysCaptor.getValue()).isNotNull().containsKey("fieldname"); - assertThat(optionsCaptor.getValue().getName()).isEqualTo("indexName"); - assertThat(optionsCaptor.getValue().isBackground()).isFalse(); - assertThat(optionsCaptor.getValue().getExpireAfter(TimeUnit.SECONDS)).isNull(); + IndexModel indexModel = indexModelCaptor.getValue().get(0); + + assertThat(indexModel.getKeys().toBsonDocument()).isNotNull().containsKey("fieldname"); + assertThat(indexModel.getOptions().getName()).isEqualTo("indexName"); + assertThat(indexModel.getOptions().isBackground()).isFalse(); + assertThat(indexModel.getOptions().getExpireAfter(TimeUnit.SECONDS)).isNull(); } @Test // DATAMONGO-1928 void createIndexShouldUsePersistenceExceptionTranslatorForNonDataIntegrityConcerns() { - when(collection.createIndex(any(org.bson.Document.class), any(IndexOptions.class))) + when(collection.createIndexes(indexModelCaptor.capture(), any(CreateIndexOptions.class))) .thenReturn(Mono.error(new MongoException(6, "HostUnreachable"))); MongoMappingContext mappingContext = prepareMappingContext(Person.class); @@ -116,7 +125,7 @@ void createIndexShouldUsePersistenceExceptionTranslatorForNonDataIntegrityConcer @Test // DATAMONGO-1928 void createIndexShouldNotConvertUnknownExceptionTypes() { - when(collection.createIndex(any(org.bson.Document.class), any(IndexOptions.class))) + when(collection.createIndexes(any(List.class), any(CreateIndexOptions.class))) .thenReturn(Mono.error(new ClassCastException("o_O"))); MongoMappingContext mappingContext = prepareMappingContext(Person.class);