From 04d0d942440f4d720fe647068edef8e0962376be Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Thu, 17 Jul 2025 15:29:22 +0200 Subject: [PATCH] Introduce factory methods for MongoDatabaseFactory for client lifecycle guidance. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We now expose create(…) methods returning either MongoDatabaseFactory when created with a client that doesn't publicly declare a destroy() method and create methods returning SimpleMongoClientDatabaseFactory when used with a connection string to indicate lifecycle-awareness for the internally created MongoClient. See #5012 --- .../data/mongodb/MongoDatabaseFactory.java | 51 +++++++++++++++++++ .../data/mongodb/MongoSessionProvider.java | 2 +- .../mongodb/ReactiveMongoDatabaseFactory.java | 50 ++++++++++++++++++ .../AbstractMongoClientConfiguration.java | 3 +- .../AbstractReactiveMongoConfiguration.java | 3 +- ...leMongoClientDatabaseFactoryUnitTests.java | 6 +-- .../mongodb/observability/TestConfig.java | 43 +++++++++------- 7 files changed, 131 insertions(+), 27 deletions(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoDatabaseFactory.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoDatabaseFactory.java index 1fcd5de51..280cc0b4b 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoDatabaseFactory.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoDatabaseFactory.java @@ -16,12 +16,17 @@ package org.springframework.data.mongodb; import org.bson.codecs.configuration.CodecRegistry; + import org.springframework.dao.DataAccessException; import org.springframework.dao.support.PersistenceExceptionTranslator; import org.springframework.data.mongodb.core.MongoExceptionTranslator; +import org.springframework.data.mongodb.core.SimpleMongoClientDatabaseFactory; +import org.springframework.util.Assert; import com.mongodb.ClientSessionOptions; +import com.mongodb.ConnectionString; import com.mongodb.client.ClientSession; +import com.mongodb.client.MongoClient; import com.mongodb.client.MongoDatabase; /** @@ -30,10 +35,56 @@ import com.mongodb.client.MongoDatabase; * @author Mark Pollack * @author Thomas Darimont * @author Christoph Strobl + * @author Mark Paluch * @since 3.0 */ public interface MongoDatabaseFactory extends CodecRegistryProvider, MongoSessionProvider { + /** + * Creates a new {@link SimpleMongoClientDatabaseFactory} instance for the given {@code connectionString}. Using this + * factory method will create a new {@link MongoClient} instance that will be closed when calling + * {@link SimpleMongoClientDatabaseFactory#destroy()}. + * + * @param connectionString connection coordinates for a database connection. Must contain a database name and must not + * be {@literal null} or empty. + * @since 4.5.2 + * @see MongoDB Connection String reference + */ + static SimpleMongoClientDatabaseFactory create(String connectionString) { + + Assert.notNull(connectionString, "ConnectionString must not be null"); + + return new SimpleMongoClientDatabaseFactory(connectionString); + } + + /** + * Creates a new {@link SimpleMongoClientDatabaseFactory} instance from the given {@link MongoClient}. Using this + * factory will create a new {@link MongoClient} instance that will be closed when calling + * {@link SimpleMongoClientDatabaseFactory#destroy()}. + * + * @param connectionString connection coordinates for a database connection. Must contain also a database name and not + * be {@literal null}. + * @since 4.5.2 + */ + static SimpleMongoClientDatabaseFactory create(ConnectionString connectionString) { + + Assert.notNull(connectionString, "ConnectionString must not be null"); + + return new SimpleMongoClientDatabaseFactory(connectionString); + } + + /** + * Creates a new {@link MongoDatabaseFactory} instance from the given {@link MongoClient}. We assume a managed client + * instance that will be disposed by you (or the application container) once the client is no longer required for use. + * + * @param mongoClient must not be {@literal null}. + * @param databaseName must not be {@literal null} or empty. + * @since 4.5.2 + */ + static MongoDatabaseFactory create(MongoClient mongoClient, String databaseName) { + return new SimpleMongoClientDatabaseFactory(mongoClient, databaseName); + } + /** * Obtain a {@link MongoDatabase} from the underlying factory. * diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoSessionProvider.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoSessionProvider.java index 645b3508d..8aa4ab5fd 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoSessionProvider.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoSessionProvider.java @@ -31,7 +31,7 @@ import com.mongodb.client.ClientSession; public interface MongoSessionProvider { /** - * Obtain a {@link ClientSession} with with given options. + * Obtain a {@link ClientSession} with given options. * * @param options must not be {@literal null}. * @return never {@literal null}. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/ReactiveMongoDatabaseFactory.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/ReactiveMongoDatabaseFactory.java index f2a6714a9..1ff6d6a72 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/ReactiveMongoDatabaseFactory.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/ReactiveMongoDatabaseFactory.java @@ -18,12 +18,17 @@ package org.springframework.data.mongodb; import reactor.core.publisher.Mono; import org.bson.codecs.configuration.CodecRegistry; + import org.springframework.dao.DataAccessException; import org.springframework.dao.support.PersistenceExceptionTranslator; import org.springframework.data.mongodb.core.MongoExceptionTranslator; +import org.springframework.data.mongodb.core.SimpleReactiveMongoDatabaseFactory; +import org.springframework.util.Assert; import com.mongodb.ClientSessionOptions; +import com.mongodb.ConnectionString; import com.mongodb.reactivestreams.client.ClientSession; +import com.mongodb.reactivestreams.client.MongoClient; import com.mongodb.reactivestreams.client.MongoDatabase; /** @@ -36,6 +41,51 @@ import com.mongodb.reactivestreams.client.MongoDatabase; */ public interface ReactiveMongoDatabaseFactory extends CodecRegistryProvider { + /** + * Creates a new {@link SimpleReactiveMongoDatabaseFactory} instance for the given {@code connectionString}. Using + * this factory method will create a new {@link MongoClient} instance that will be closed when calling + * {@link SimpleReactiveMongoDatabaseFactory#destroy()}. + * + * @param connectionString connection coordinates for a database connection. Must contain a database name and must not + * be {@literal null} or empty. + * @since 4.5.2 + * @see MongoDB Connection String reference + */ + static SimpleReactiveMongoDatabaseFactory create(String connectionString) { + + Assert.notNull(connectionString, "ConnectionString must not be null"); + + return create(new ConnectionString(connectionString)); + } + + /** + * Creates a new {@link SimpleReactiveMongoDatabaseFactory} instance from the given {@link MongoClient}. Using this + * factory will create a new {@link MongoClient} instance that will be closed when calling + * {@link SimpleReactiveMongoDatabaseFactory#destroy()}. + * + * @param connectionString connection coordinates for a database connection. Must contain also a database name and not + * be {@literal null}. + * @since 4.5.2 + */ + static SimpleReactiveMongoDatabaseFactory create(ConnectionString connectionString) { + + Assert.notNull(connectionString, "ConnectionString must not be null"); + + return new SimpleReactiveMongoDatabaseFactory(connectionString); + } + + /** + * Creates a new {@link MongoDatabaseFactory} instance from the given {@link MongoClient}. We assume a managed client + * instance that will be disposed by you (or the application container) once the client is no longer required for use. + * + * @param mongoClient must not be {@literal null}. + * @param databaseName must not be {@literal null} or empty. + * @since 4.5.2 + */ + static ReactiveMongoDatabaseFactory create(MongoClient mongoClient, String databaseName) { + return new SimpleReactiveMongoDatabaseFactory(mongoClient, databaseName); + } + /** * Creates a default {@link MongoDatabase} instance. * diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/AbstractMongoClientConfiguration.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/AbstractMongoClientConfiguration.java index 93033417f..e5d097564 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/AbstractMongoClientConfiguration.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/AbstractMongoClientConfiguration.java @@ -20,7 +20,6 @@ import org.springframework.context.annotation.Configuration; import org.springframework.data.mongodb.MongoDatabaseFactory; import org.springframework.data.mongodb.SpringDataMongoDB; import org.springframework.data.mongodb.core.MongoTemplate; -import org.springframework.data.mongodb.core.SimpleMongoClientDatabaseFactory; import org.springframework.data.mongodb.core.convert.DbRefResolver; import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver; import org.springframework.data.mongodb.core.convert.MappingMongoConverter; @@ -75,7 +74,7 @@ public abstract class AbstractMongoClientConfiguration extends MongoConfiguratio */ @Bean public MongoDatabaseFactory mongoDbFactory() { - return new SimpleMongoClientDatabaseFactory(mongoClient(), getDatabaseName()); + return MongoDatabaseFactory.create(mongoClient(), getDatabaseName()); } /** diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/AbstractReactiveMongoConfiguration.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/AbstractReactiveMongoConfiguration.java index f93c4ae70..02867df09 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/AbstractReactiveMongoConfiguration.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/config/AbstractReactiveMongoConfiguration.java @@ -21,7 +21,6 @@ import org.springframework.data.mongodb.ReactiveMongoDatabaseFactory; import org.springframework.data.mongodb.SpringDataMongoDB; import org.springframework.data.mongodb.core.ReactiveMongoOperations; import org.springframework.data.mongodb.core.ReactiveMongoTemplate; -import org.springframework.data.mongodb.core.SimpleReactiveMongoDatabaseFactory; import org.springframework.data.mongodb.core.convert.MappingMongoConverter; import org.springframework.data.mongodb.core.convert.MongoCustomConversions; import org.springframework.data.mongodb.core.convert.NoOpDbRefResolver; @@ -79,7 +78,7 @@ public abstract class AbstractReactiveMongoConfiguration extends MongoConfigurat */ @Bean public ReactiveMongoDatabaseFactory reactiveMongoDbFactory() { - return new SimpleReactiveMongoDatabaseFactory(reactiveMongoClient(), getDatabaseName()); + return ReactiveMongoDatabaseFactory.create(reactiveMongoClient(), getDatabaseName()); } /** diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/SimpleMongoClientDatabaseFactoryUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/SimpleMongoClientDatabaseFactoryUnitTests.java index 5e64eed4f..0a7ef4fd2 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/SimpleMongoClientDatabaseFactoryUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/SimpleMongoClientDatabaseFactoryUnitTests.java @@ -72,7 +72,7 @@ class SimpleMongoClientDatabaseFactoryUnitTests { ConnectionString mongoURI = new ConnectionString( "mongodb://myUsername:myPassword@localhost/myDatabase.myCollection"); - MongoDatabaseFactory mongoDbFactory = new SimpleMongoClientDatabaseFactory(mongoURI); + SimpleMongoClientDatabaseFactory mongoDbFactory = MongoDatabaseFactory.create(mongoURI); assertThat(mongoDbFactory).hasFieldOrPropertyWithValue("databaseName", "myDatabase"); } @@ -82,7 +82,7 @@ class SimpleMongoClientDatabaseFactoryUnitTests { ConnectionString uri = new ConnectionString( "mongodb://myUserName:myPassWord@127.0.0.1:27017/myDataBase.myCollection"); - SimpleMongoClientDatabaseFactory factory = new SimpleMongoClientDatabaseFactory(uri); + SimpleMongoClientDatabaseFactory factory = MongoDatabaseFactory.create(uri); assertThat(factory).hasFieldOrPropertyWithValue("databaseName", "myDataBase"); } @@ -92,7 +92,7 @@ class SimpleMongoClientDatabaseFactoryUnitTests { when(mongo.getDatabase("foo")).thenReturn(database); - MongoDatabaseFactory factory = new SimpleMongoClientDatabaseFactory(mongo, "foo"); + MongoDatabaseFactory factory = MongoDatabaseFactory.create(mongo, "foo"); MongoDatabaseFactory wrapped = factory.withSession(clientSession).withSession(clientSession); InvocationHandler invocationHandler = Proxy.getInvocationHandler(wrapped.getMongoDatabase()); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/observability/TestConfig.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/observability/TestConfig.java index 7e7e2c636..73ba8c7c9 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/observability/TestConfig.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/observability/TestConfig.java @@ -15,6 +15,13 @@ */ package org.springframework.data.mongodb.observability; +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.observation.DefaultMeterObservationHandler; +import io.micrometer.core.instrument.simple.SimpleMeterRegistry; +import io.micrometer.observation.ObservationRegistry; +import io.micrometer.tracing.Tracer; +import io.micrometer.tracing.test.simple.SimpleTracer; + import java.util.Properties; import org.springframework.beans.factory.config.PropertiesFactoryBean; @@ -27,8 +34,6 @@ import org.springframework.data.mongodb.core.MongoOperations; import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.ReactiveMongoOperations; import org.springframework.data.mongodb.core.ReactiveMongoTemplate; -import org.springframework.data.mongodb.core.SimpleMongoClientDatabaseFactory; -import org.springframework.data.mongodb.core.SimpleReactiveMongoDatabaseFactory; import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver; import org.springframework.data.mongodb.core.convert.MappingMongoConverter; import org.springframework.data.mongodb.core.convert.MongoConverter; @@ -43,15 +48,9 @@ import org.springframework.data.repository.core.support.PropertiesBasedNamedQuer import com.mongodb.ConnectionString; import com.mongodb.MongoClientSettings; +import com.mongodb.client.MongoClient; import com.mongodb.client.MongoClients; -import io.micrometer.core.instrument.MeterRegistry; -import io.micrometer.core.instrument.observation.DefaultMeterObservationHandler; -import io.micrometer.core.instrument.simple.SimpleMeterRegistry; -import io.micrometer.observation.ObservationRegistry; -import io.micrometer.tracing.Tracer; -import io.micrometer.tracing.test.simple.SimpleTracer; - /** * @author Mark Paluch */ @@ -66,13 +65,23 @@ class TestConfig { } @Bean - MongoDatabaseFactory mongoDatabaseFactory(MongoClientSettings settings) { - return new SimpleMongoClientDatabaseFactory(MongoClients.create(settings), "observable"); + MongoClient mongoClient(MongoClientSettings settings) { + return MongoClients.create(settings); + } + + @Bean + MongoDatabaseFactory mongoDatabaseFactory(MongoClient client) { + return MongoDatabaseFactory.create(client, "observable"); } @Bean - ReactiveMongoDatabaseFactory reactiveMongoDatabaseFactory(MongoClientSettings settings) { - return new SimpleReactiveMongoDatabaseFactory(com.mongodb.reactivestreams.client.MongoClients.create(settings), + com.mongodb.reactivestreams.client.MongoClient reactiveMongoClient(MongoClientSettings settings) { + return com.mongodb.reactivestreams.client.MongoClients.create(settings); + } + + @Bean + ReactiveMongoDatabaseFactory reactiveMongoDatabaseFactory(com.mongodb.reactivestreams.client.MongoClient client) { + return ReactiveMongoDatabaseFactory.create(client, "observable"); } @@ -103,17 +112,13 @@ class TestConfig { @Bean MongoTemplate mongoTemplate(MongoDatabaseFactory mongoDatabaseFactory, MongoConverter mongoConverter) { - - MongoTemplate template = new MongoTemplate(mongoDatabaseFactory, mongoConverter); - return template; + return new MongoTemplate(mongoDatabaseFactory, mongoConverter); } @Bean ReactiveMongoTemplate reactiveMongoTemplate(ReactiveMongoDatabaseFactory mongoDatabaseFactory, MongoConverter mongoConverter) { - - ReactiveMongoTemplate template = new ReactiveMongoTemplate(mongoDatabaseFactory, mongoConverter); - return template; + return new ReactiveMongoTemplate(mongoDatabaseFactory, mongoConverter); } @Bean