sessionProvide
/**
* Create an uncapped collection with a name based on the provided entity class.
+ *
+ * This method derives {@link CollectionOptions} from the given {@code entityClass} using
+ * {@link org.springframework.data.mongodb.core.mapping.Document} and
+ * {@link org.springframework.data.mongodb.core.mapping.TimeSeries} annotations to determine:
+ *
+ * - Collation
+ * - TimeSeries time and meta fields, granularity and {@code expireAfter}
+ *
+ * Any other options such as change stream options, schema-based details (validation, encryption) are not considered
+ * and must be provided through {@link #createCollection(Class, Function)} or
+ * {@link #createCollection(Class, CollectionOptions)}.
*
* @param entityClass class that determines the collection to create.
* @return the created collection.
+ * @see #createCollection(Class, Function)
+ * @see #createCollection(Class, CollectionOptions)
*/
Mono> createCollection(Class entityClass);
+ /**
+ * Create an uncapped collection with a name based on the provided entity class allowing to customize derived
+ * {@link CollectionOptions}.
+ *
+ * This method derives {@link CollectionOptions} from the given {@code entityClass} using
+ * {@link org.springframework.data.mongodb.core.mapping.Document} and
+ * {@link org.springframework.data.mongodb.core.mapping.TimeSeries} annotations to determine:
+ *
+ * - Collation
+ * - TimeSeries time and meta fields, granularity and {@code expireAfter}
+ *
+ * Any other options such as change stream options, schema-based details (validation, encryption) are not considered
+ * and must be provided through {@link CollectionOptions}.
+ *
+ * @param entityClass class that determines the collection to create.
+ * @param collectionOptionsCustomizer customizer function to customize the derived {@link CollectionOptions}.
+ * @return the created collection.
+ * @see #createCollection(Class, CollectionOptions)
+ * @since 5.0
+ */
+ Mono> createCollection(Class entityClass,
+ Function super CollectionOptions, ? extends CollectionOptions> collectionOptionsCustomizer);
+
/**
* Create a collection with a name based on the provided entity class using the options.
*
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 0ad473b8b7..232f35cd1b 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
@@ -659,7 +659,17 @@ public Mono createMono(String collectionName, ReactiveCollectionCallback<
@Override
public Mono> createCollection(Class entityClass) {
- return createCollection(entityClass, operations.forType(entityClass).getCollectionOptions());
+ return createCollection(entityClass, Function.identity());
+ }
+
+ @Override
+ public Mono> createCollection(Class entityClass,
+ Function super CollectionOptions, ? extends CollectionOptions> collectionOptionsCustomizer) {
+
+ Assert.notNull(collectionOptionsCustomizer, "CollectionOptions customizer function must not be null");
+
+ return createCollection(entityClass,
+ collectionOptionsCustomizer.apply(operations.forType(entityClass).getCollectionOptions()));
}
@Override
@@ -740,11 +750,10 @@ public Mono collectionExists(Class entityClass) {
@Override
public Mono collectionExists(String collectionName) {
- return createMono(
- db -> Flux.from(db.listCollectionNames()) //
- .filter(s -> s.equals(collectionName)) //
- .map(s -> true) //
- .single(false));
+ return createMono(db -> Flux.from(db.listCollectionNames()) //
+ .filter(s -> s.equals(collectionName)) //
+ .map(s -> true) //
+ .single(false));
}
@Override
@@ -2293,7 +2302,7 @@ protected Flux doFindAndDelete(String collectionName, Query query, Class<
.flatMapSequential(deleteResult -> Flux.fromIterable(list)));
}
- @SuppressWarnings({"rawtypes", "unchecked", "NullAway"})
+ @SuppressWarnings({ "rawtypes", "unchecked", "NullAway" })
Flux doFindAndDelete(String collectionName, Query query, Class entityClass,
QueryResultConverter super S, ? extends T> resultConverter) {
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/Document.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/Document.java
index ef4980fab6..cf6001b8e8 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/Document.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/Document.java
@@ -42,8 +42,8 @@
/**
* The collection the document representing the entity is supposed to be stored in. If not configured, a default
* collection name will be derived from the type's name. The attribute supports SpEL expressions to dynamically
- * calculate the collection to based on a per operation basis.
- *
+ * calculate the collection to based on a per-operation basis.
+ *
* @return the name of the collection to be used.
*/
@AliasFor("collection")
@@ -53,7 +53,7 @@
* The collection the document representing the entity is supposed to be stored in. If not configured, a default
* collection name will be derived from the type's name. The attribute supports SpEL expressions to dynamically
* calculate the collection to based on a per operation basis.
- *
+ *
* @return the name of the collection to be used.
*/
@AliasFor("value")
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/TimeSeries.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/TimeSeries.java
index efe0cd8703..e3293e74b6 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/TimeSeries.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/TimeSeries.java
@@ -41,7 +41,7 @@
/**
* The collection the document representing the entity is supposed to be stored in. If not configured, a default
* collection name will be derived from the type's name. The attribute supports SpEL expressions to dynamically
- * calculate the collection based on a per operation basis.
+ * calculate the collection based on a per-operation basis.
*
* @return the name of the collection to be used.
* @see Document#collection()
diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateUnitTests.java
index ef72548fac..177764e4c5 100644
--- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateUnitTests.java
+++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateUnitTests.java
@@ -186,11 +186,13 @@ void beforeEach() {
when(collection.aggregate(any(List.class), any())).thenReturn(aggregateIterable);
when(collection.withReadConcern(any())).thenReturn(collection);
when(collection.withReadPreference(any())).thenReturn(collection);
- when(collection.replaceOne(any(), any(), any(com.mongodb.client.model.ReplaceOptions.class))).thenReturn(updateResult);
+ when(collection.replaceOne(any(), any(), any(com.mongodb.client.model.ReplaceOptions.class)))
+ .thenReturn(updateResult);
when(collection.withWriteConcern(any())).thenReturn(collectionWithWriteConcern);
when(collection.distinct(anyString(), any(Document.class), any())).thenReturn(distinctIterable);
when(collectionWithWriteConcern.deleteOne(any(Bson.class), any())).thenReturn(deleteResult);
- when(collectionWithWriteConcern.replaceOne(any(), any(), any(com.mongodb.client.model.ReplaceOptions.class))).thenReturn(updateResult);
+ when(collectionWithWriteConcern.replaceOne(any(), any(), any(com.mongodb.client.model.ReplaceOptions.class)))
+ .thenReturn(updateResult);
when(findIterable.projection(any())).thenReturn(findIterable);
when(findIterable.sort(any(org.bson.Document.class))).thenReturn(findIterable);
when(findIterable.collation(any())).thenReturn(findIterable);
@@ -1263,7 +1265,8 @@ void saveVersionedEntityShouldCallUpdateCorrectly() {
template.save(entity);
- verify(collection, times(1)).replaceOne(queryCaptor.capture(), updateCaptor.capture(), any(com.mongodb.client.model.ReplaceOptions.class));
+ verify(collection, times(1)).replaceOne(queryCaptor.capture(), updateCaptor.capture(),
+ any(com.mongodb.client.model.ReplaceOptions.class));
assertThat(queryCaptor.getValue()).isEqualTo(new Document("_id", 1).append("version", 10));
assertThat(updateCaptor.getValue())
@@ -1399,10 +1402,14 @@ void createCollectionShouldNotCollationIfNotPresent() {
Assertions.assertThat(options.getValue().getCollation()).isNull();
}
- @Test // DATAMONGO-1854
+ @Test // DATAMONGO-1854, GH-4978
void createCollectionShouldApplyDefaultCollation() {
- template.createCollection(Sith.class);
+ template.createCollection(Sith.class, options -> {
+
+ assertThat(options.getCollation()).contains(Collation.of("de_AT"));
+ return options;
+ });
ArgumentCaptor options = ArgumentCaptor.forClass(CreateCollectionOptions.class);
verify(db).createCollection(any(), options.capture());
@@ -1426,7 +1433,7 @@ void createCollectionShouldFavorExplicitOptionsOverDefaultCollation() {
@Test // DATAMONGO-1854
void createCollectionShouldUseDefaultCollationIfCollectionOptionsAreNull() {
- template.createCollection(Sith.class, null);
+ template.createCollection(Sith.class, (CollectionOptions) null);
ArgumentCaptor options = ArgumentCaptor.forClass(CreateCollectionOptions.class);
verify(db).createCollection(any(), options.capture());
@@ -2399,8 +2406,7 @@ void createCollectionShouldSetUpTimeSeriesWithExpirationFromString() {
ArgumentCaptor options = ArgumentCaptor.forClass(CreateCollectionOptions.class);
verify(db).createCollection(any(), options.capture());
- assertThat(options.getValue().getExpireAfter(TimeUnit.MINUTES))
- .isEqualTo(10);
+ assertThat(options.getValue().getExpireAfter(TimeUnit.MINUTES)).isEqualTo(10);
}
@Test // GH-4099
@@ -2413,8 +2419,7 @@ void createCollectionShouldSetUpTimeSeriesWithExpirationFromProperty() {
ArgumentCaptor options = ArgumentCaptor.forClass(CreateCollectionOptions.class);
verify(db).createCollection(any(), options.capture());
- assertThat(options.getValue().getExpireAfter(TimeUnit.MINUTES))
- .isEqualTo(12);
+ assertThat(options.getValue().getExpireAfter(TimeUnit.MINUTES)).isEqualTo(12);
}
@Test // GH-4099
@@ -2425,8 +2430,7 @@ void createCollectionShouldSetUpTimeSeriesWithExpirationFromIso8601String() {
ArgumentCaptor options = ArgumentCaptor.forClass(CreateCollectionOptions.class);
verify(db).createCollection(any(), options.capture());
- assertThat(options.getValue().getExpireAfter(TimeUnit.DAYS))
- .isEqualTo(1);
+ assertThat(options.getValue().getExpireAfter(TimeUnit.DAYS)).isEqualTo(1);
}
@Test // GH-4099
@@ -2437,8 +2441,7 @@ void createCollectionShouldSetUpTimeSeriesWithExpirationFromExpression() {
ArgumentCaptor options = ArgumentCaptor.forClass(CreateCollectionOptions.class);
verify(db).createCollection(any(), options.capture());
- assertThat(options.getValue().getExpireAfter(TimeUnit.SECONDS))
- .isEqualTo(11);
+ assertThat(options.getValue().getExpireAfter(TimeUnit.SECONDS)).isEqualTo(11);
}
@Test // GH-4099
@@ -2449,16 +2452,14 @@ void createCollectionShouldSetUpTimeSeriesWithExpirationFromExpressionReturningD
ArgumentCaptor options = ArgumentCaptor.forClass(CreateCollectionOptions.class);
verify(db).createCollection(any(), options.capture());
- assertThat(options.getValue().getExpireAfter(TimeUnit.SECONDS))
- .isEqualTo(100);
+ assertThat(options.getValue().getExpireAfter(TimeUnit.SECONDS)).isEqualTo(100);
}
@Test // GH-4099
void createCollectionShouldSetUpTimeSeriesWithInvalidTimeoutExpiration() {
- assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() ->
- template.createCollection(TimeSeriesTypeWithInvalidExpireAfter.class)
- );
+ assertThatExceptionOfType(IllegalArgumentException.class)
+ .isThrownBy(() -> template.createCollection(TimeSeriesTypeWithInvalidExpireAfter.class));
}
@Test // GH-3522
@@ -2611,32 +2612,31 @@ public WriteConcern resolve(MongoAction action) {
verify(collection).withWriteConcern(eq(WriteConcern.UNACKNOWLEDGED));
}
- @Test // GH-4099
- void passOnTimeSeriesExpireOption() {
-
- template.createCollection("time-series-collection",
- CollectionOptions.timeSeries("time_stamp", options -> options.expireAfter(Duration.ofSeconds(10))));
+ @Test // GH-4099
+ void passOnTimeSeriesExpireOption() {
- ArgumentCaptor options = ArgumentCaptor.forClass(CreateCollectionOptions.class);
- verify(db).createCollection(any(), options.capture());
+ template.createCollection("time-series-collection",
+ CollectionOptions.timeSeries("time_stamp", options -> options.expireAfter(Duration.ofSeconds(10))));
- assertThat(options.getValue().getExpireAfter(TimeUnit.SECONDS)).isEqualTo(10);
- }
+ ArgumentCaptor options = ArgumentCaptor.forClass(CreateCollectionOptions.class);
+ verify(db).createCollection(any(), options.capture());
- @Test // GH-4099
- void doNotSetTimeSeriesExpireOptionForNegativeValue() {
+ assertThat(options.getValue().getExpireAfter(TimeUnit.SECONDS)).isEqualTo(10);
+ }
- template.createCollection("time-series-collection",
- CollectionOptions.timeSeries("time_stamp", options -> options.expireAfter(Duration.ofSeconds(-10))));
+ @Test // GH-4099
+ void doNotSetTimeSeriesExpireOptionForNegativeValue() {
- ArgumentCaptor options = ArgumentCaptor.forClass(CreateCollectionOptions.class);
- verify(db).createCollection(any(), options.capture());
+ template.createCollection("time-series-collection",
+ CollectionOptions.timeSeries("time_stamp", options -> options.expireAfter(Duration.ofSeconds(-10))));
- assertThat(options.getValue().getExpireAfter(TimeUnit.SECONDS)).isEqualTo(0L);
- }
+ ArgumentCaptor options = ArgumentCaptor.forClass(CreateCollectionOptions.class);
+ verify(db).createCollection(any(), options.capture());
+ assertThat(options.getValue().getExpireAfter(TimeUnit.SECONDS)).isEqualTo(0L);
+ }
- class AutogenerateableId {
+ class AutogenerateableId {
@Id BigInteger id;
}
diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveMongoTemplateUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveMongoTemplateUnitTests.java
index 36cf0886ad..97f22378dd 100644
--- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveMongoTemplateUnitTests.java
+++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveMongoTemplateUnitTests.java
@@ -456,8 +456,10 @@ void doesNotApplyFieldsWhenInterfaceProjectionIsClosedAndQueryDefinesFields() {
@Test // DATAMONGO-1719
void doesNotApplyFieldsWhenInterfaceProjectionIsOpen() {
- template.doFind("star-wars", CollectionPreparer.identity(), new Document(), new Document(), Person.class,
- PersonSpELProjection.class, QueryResultConverter.entity(), FindPublisherPreparer.NO_OP_PREPARER).subscribe();
+ template
+ .doFind("star-wars", CollectionPreparer.identity(), new Document(), new Document(), Person.class,
+ PersonSpELProjection.class, QueryResultConverter.entity(), FindPublisherPreparer.NO_OP_PREPARER)
+ .subscribe();
verify(findPublisher, never()).projection(any());
}
@@ -638,10 +640,14 @@ void createCollectionShouldNotCollationIfNotPresent() {
Assertions.assertThat(options.getValue().getCollation()).isNull();
}
- @Test // DATAMONGO-1854
+ @Test // DATAMONGO-1854, GH-4978
void createCollectionShouldApplyDefaultCollation() {
- template.createCollection(Sith.class).subscribe();
+ template.createCollection(Sith.class, options -> {
+
+ assertThat(options.getCollation()).contains(Collation.of("de_AT"));
+ return options;
+ }).subscribe();
ArgumentCaptor options = ArgumentCaptor.forClass(CreateCollectionOptions.class);
verify(db).createCollection(any(), options.capture());
@@ -665,7 +671,7 @@ void createCollectionShouldFavorExplicitOptionsOverDefaultCollation() {
@Test // DATAMONGO-1854
void createCollectionShouldUseDefaultCollationIfCollectionOptionsAreNull() {
- template.createCollection(Sith.class, null).subscribe();
+ template.createCollection(Sith.class, (CollectionOptions) null).subscribe();
ArgumentCaptor options = ArgumentCaptor.forClass(CreateCollectionOptions.class);
verify(db).createCollection(any(), options.capture());
@@ -1751,8 +1757,7 @@ void createCollectionShouldSetUpTimeSeriesWithExpirationFromString() {
ArgumentCaptor options = ArgumentCaptor.forClass(CreateCollectionOptions.class);
verify(db).createCollection(any(), options.capture());
- assertThat(options.getValue().getExpireAfter(TimeUnit.MINUTES))
- .isEqualTo(10);
+ assertThat(options.getValue().getExpireAfter(TimeUnit.MINUTES)).isEqualTo(10);
}
@Test // GH-4099
@@ -1763,8 +1768,7 @@ void createCollectionShouldSetUpTimeSeriesWithExpirationFromIso8601String() {
ArgumentCaptor options = ArgumentCaptor.forClass(CreateCollectionOptions.class);
verify(db).createCollection(any(), options.capture());
- assertThat(options.getValue().getExpireAfter(TimeUnit.DAYS))
- .isEqualTo(1);
+ assertThat(options.getValue().getExpireAfter(TimeUnit.DAYS)).isEqualTo(1);
}
@Test // GH-4099
@@ -1775,8 +1779,7 @@ void createCollectionShouldSetUpTimeSeriesWithExpirationFromExpression() {
ArgumentCaptor options = ArgumentCaptor.forClass(CreateCollectionOptions.class);
verify(db).createCollection(any(), options.capture());
- assertThat(options.getValue().getExpireAfter(TimeUnit.SECONDS))
- .isEqualTo(11);
+ assertThat(options.getValue().getExpireAfter(TimeUnit.SECONDS)).isEqualTo(11);
}
@Test // GH-4099
@@ -1787,16 +1790,14 @@ void createCollectionShouldSetUpTimeSeriesWithExpirationFromExpressionReturningD
ArgumentCaptor options = ArgumentCaptor.forClass(CreateCollectionOptions.class);
verify(db).createCollection(any(), options.capture());
- assertThat(options.getValue().getExpireAfter(TimeUnit.SECONDS))
- .isEqualTo(100);
+ assertThat(options.getValue().getExpireAfter(TimeUnit.SECONDS)).isEqualTo(100);
}
@Test // GH-4099
void createCollectionShouldSetUpTimeSeriesWithInvalidTimeoutExpiration() {
- assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() ->
- template.createCollection(TimeSeriesTypeWithInvalidExpireAfter.class).subscribe()
- );
+ assertThatExceptionOfType(IllegalArgumentException.class)
+ .isThrownBy(() -> template.createCollection(TimeSeriesTypeWithInvalidExpireAfter.class).subscribe());
}
private void stubFindSubscribe(Document document) {
diff --git a/src/main/antora/modules/ROOT/pages/mongodb/mongo-encryption.adoc b/src/main/antora/modules/ROOT/pages/mongodb/mongo-encryption.adoc
index 14e866cf14..a8b4a8c519 100644
--- a/src/main/antora/modules/ROOT/pages/mongodb/mongo-encryption.adoc
+++ b/src/main/antora/modules/ROOT/pages/mongodb/mongo-encryption.adoc
@@ -188,7 +188,7 @@ It is possible to create custom annotations out of the provided ones.
MongoDB Collection Info::
+
====
-[source,java,indent=0,subs="verbatim,quotes",role="thrid"]
+[source,jsonc,indent=0,subs="verbatim,quotes",role="thrid"]
----
{
name: 'patient',