Add support for com.mongodb.client.MongoClient

Next to com.mongodb.MongoClient the MongoDB Java driver offers the
com.mongodb.client.MongoClient as entry point for database and
collection operations. Spring Data MongoDB supports
c.m.client.MongoClient via its MongoDbFactory using
SimpleMongoClientDbFactory.

The MongoAutoConfiguration now backs off if any of those two clients is
already defined in the Application context allowing
MongoDataAutoConfiguration to pick up the users driver implementation of
choice.

See gh-14176
This commit is contained in:
Christoph Strobl 2018-08-22 13:55:28 +02:00 committed by Stephane Nicoll
parent 19d17e7ec9
commit d549e6001a
4 changed files with 155 additions and 6 deletions

View File

@ -16,27 +16,35 @@
package org.springframework.boot.autoconfigure.data.mongo; package org.springframework.boot.autoconfigure.data.mongo;
import java.util.Arrays;
import java.util.List;
import com.mongodb.ClientSessionOptions; import com.mongodb.ClientSessionOptions;
import com.mongodb.DB; import com.mongodb.DB;
import com.mongodb.MongoClient; import com.mongodb.MongoClient;
import com.mongodb.client.ClientSession; import com.mongodb.client.ClientSession;
import com.mongodb.client.MongoDatabase; import com.mongodb.client.MongoDatabase;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration.AnySyncMongoClientAvailable;
import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration; import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
import org.springframework.boot.autoconfigure.mongo.MongoProperties; import org.springframework.boot.autoconfigure.mongo.MongoProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import org.springframework.dao.DataAccessException; import org.springframework.dao.DataAccessException;
import org.springframework.dao.support.PersistenceExceptionTranslator; import org.springframework.dao.support.PersistenceExceptionTranslator;
import org.springframework.data.mongodb.MongoDbFactory; import org.springframework.data.mongodb.MongoDbFactory;
import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.SimpleMongoClientDbFactory;
import org.springframework.data.mongodb.core.SimpleMongoDbFactory; import org.springframework.data.mongodb.core.SimpleMongoDbFactory;
import org.springframework.data.mongodb.core.convert.DbRefResolver; import org.springframework.data.mongodb.core.convert.DbRefResolver;
import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver; import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver;
@ -63,11 +71,12 @@ import org.springframework.util.StringUtils;
* @author Phillip Webb * @author Phillip Webb
* @author Eddú Meléndez * @author Eddú Meléndez
* @author Stephane Nicoll * @author Stephane Nicoll
* @author Christoph Strobl
* @since 1.1.0 * @since 1.1.0
*/ */
@Configuration @Configuration
@ConditionalOnClass({ MongoClient.class, MongoTemplate.class }) @ConditionalOnClass({ MongoClient.class, MongoTemplate.class })
@ConditionalOnBean(MongoClient.class) @Conditional(AnySyncMongoClientAvailable.class)
@EnableConfigurationProperties(MongoProperties.class) @EnableConfigurationProperties(MongoProperties.class)
@Import(MongoDataConfiguration.class) @Import(MongoDataConfiguration.class)
@AutoConfigureAfter(MongoAutoConfiguration.class) @AutoConfigureAfter(MongoAutoConfiguration.class)
@ -75,15 +84,22 @@ public class MongoDataAutoConfiguration {
private final MongoProperties properties; private final MongoProperties properties;
public MongoDataAutoConfiguration(MongoProperties properties) { private final MongoDbFactoryFactory dbFactoryFactory;
public MongoDataAutoConfiguration(ObjectProvider<MongoClient> mongoClientProvider,
ObjectProvider<com.mongodb.client.MongoClient> mongoClientClientProvider,
MongoProperties properties) {
this.properties = properties; this.properties = properties;
this.dbFactoryFactory = new MongoDbFactoryFactory(mongoClientProvider,
mongoClientClientProvider);
} }
@Bean @Bean
@Conditional(AnySyncMongoClientAvailable.class)
@ConditionalOnMissingBean(MongoDbFactory.class) @ConditionalOnMissingBean(MongoDbFactory.class)
public SimpleMongoDbFactory mongoDbFactory(MongoClient mongo) { public MongoDbFactory mongoDbFactory() {
String database = this.properties.getMongoClientDatabase(); return this.dbFactoryFactory.getFor(this.properties.getMongoClientDatabase());
return new SimpleMongoDbFactory(mongo, database);
} }
@Bean @Bean
@ -166,4 +182,90 @@ public class MongoDataAutoConfiguration {
} }
/**
* Check if either {@link com.mongodb.MongoClient} or
* {@link com.mongodb.client.MongoClient} is already defined in the
* {@link org.springframework.context.ApplicationContext}.
*
* @author Christoph Strobl
* @since 2.1
*/
static class AnySyncMongoClientAvailable extends AnyNestedCondition {
AnySyncMongoClientAvailable() {
super(ConfigurationPhase.REGISTER_BEAN);
}
@ConditionalOnBean(com.mongodb.MongoClient.class)
static class MongoClientPreferred {
}
@ConditionalOnBean(com.mongodb.client.MongoClient.class)
static class MongoClientClientPreferred {
}
}
/**
* Encapsulation of {@link MongoDbFactory} creation depending on available beans
* {@link com.mongodb.MongoClient} or {@link com.mongodb.client.MongoClient} expressed
* via the given {@link ObjectProvider ObjectProviders}. Prefers the first available
* MongoDB client creating a suitable instance of {@link MongoDbFactory} for it.
*
* @author Christoph Strobl
* @since 2.1
*/
static class MongoDbFactoryFactory {
private final List<ObjectProvider<?>> clientProviders;
/**
* Create new instance of {@link MongoDbFactoryFactory}.
* @param clientProviders order matters here, as we choose the first available
* one.
*/
MongoDbFactoryFactory(ObjectProvider<?>... clientProviders) {
this.clientProviders = Arrays.asList(clientProviders);
}
/**
* Get the {@link MongoDbFactory} suitable for the first available MongoDB client.
* @param database the name of the default database to return on
* {@link MongoDbFactory#getDb()}.
* @return new instance of {@link MongoDbFactory} suitable for the first available
* MongoDB client.
*/
MongoDbFactory getFor(String database) {
Object client = findAvailableClientProvider();
if (client instanceof MongoClient) {
return new SimpleMongoDbFactory(MongoClient.class.cast(client), database);
}
if (client instanceof com.mongodb.client.MongoClient) {
return new SimpleMongoClientDbFactory(
com.mongodb.client.MongoClient.class.cast(client), database);
}
return null;
}
private Object findAvailableClientProvider() {
for (ObjectProvider<?> provider : this.clientProviders) {
Object client = provider.getIfAvailable();
if (client != null) {
return client;
}
}
throw new IllegalStateException(
"Expected to find at least one MongoDB client.");
}
}
} }

View File

@ -65,7 +65,8 @@ public class MongoAutoConfiguration {
} }
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean(type = { "com.mongodb.MongoClient",
"com.mongodb.client.MongoClient" })
public MongoClient mongo() { public MongoClient mongo() {
this.mongo = this.factory.createMongoClient(this.options); this.mongo = this.factory.createMongoClient(this.options);
return this.mongo; return this.mongo;

View File

@ -21,6 +21,7 @@ import java.util.Arrays;
import java.util.Set; import java.util.Set;
import com.mongodb.MongoClient; import com.mongodb.MongoClient;
import com.mongodb.client.MongoClients;
import org.junit.Test; import org.junit.Test;
import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.BeanCreationException;
@ -39,7 +40,9 @@ import org.springframework.core.convert.converter.Converter;
import org.springframework.data.mapping.model.CamelCaseAbbreviatingFieldNamingStrategy; import org.springframework.data.mapping.model.CamelCaseAbbreviatingFieldNamingStrategy;
import org.springframework.data.mapping.model.FieldNamingStrategy; import org.springframework.data.mapping.model.FieldNamingStrategy;
import org.springframework.data.mapping.model.PropertyNameFieldNamingStrategy; import org.springframework.data.mapping.model.PropertyNameFieldNamingStrategy;
import org.springframework.data.mongodb.MongoDbFactory;
import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.SimpleMongoClientDbFactory;
import org.springframework.data.mongodb.core.convert.MongoCustomConversions; import org.springframework.data.mongodb.core.convert.MongoCustomConversions;
import org.springframework.data.mongodb.core.mapping.BasicMongoPersistentEntity; import org.springframework.data.mongodb.core.mapping.BasicMongoPersistentEntity;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext; import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
@ -173,6 +176,16 @@ public class MongoDataAutoConfigurationTests {
.doesNotHaveBean(MongoDataAutoConfiguration.class)); .doesNotHaveBean(MongoDataAutoConfiguration.class));
} }
@Test
public void createsMongoDbFactoryForMongoClientClientWhenBeanPresent() {
this.contextRunner.withUserConfiguration(WithMongoClientClientConfiguration.class)
.run((context) -> {
MongoDbFactory dbFactory = context.getBean(MongoDbFactory.class);
assertThat(dbFactory).isInstanceOf(SimpleMongoClientDbFactory.class);
});
}
@SuppressWarnings({ "unchecked", "rawtypes" }) @SuppressWarnings({ "unchecked", "rawtypes" })
private static void assertDomainTypesDiscovered(MongoMappingContext mappingContext, private static void assertDomainTypesDiscovered(MongoMappingContext mappingContext,
Class<?>... types) { Class<?>... types) {
@ -197,6 +210,16 @@ public class MongoDataAutoConfigurationTests {
} }
@Configuration
static class WithMongoClientClientConfiguration {
@Bean
com.mongodb.client.MongoClient mongoClient() {
return MongoClients.create();
}
}
private static class MyConverter implements Converter<MongoClient, Boolean> { private static class MyConverter implements Converter<MongoClient, Boolean> {
@Override @Override

View File

@ -20,14 +20,17 @@ import javax.net.SocketFactory;
import com.mongodb.MongoClient; import com.mongodb.MongoClient;
import com.mongodb.MongoClientOptions; import com.mongodb.MongoClientOptions;
import com.mongodb.client.MongoClients;
import org.junit.Test; import org.junit.Test;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
/** /**
@ -78,6 +81,17 @@ public class MongoAutoConfigurationTests {
}); });
} }
@Test
public void doesNotCreateMongoClientWhenAlreadyDefined() {
this.contextRunner
.withPropertyValues("spring.data.mongodb.uri:mongodb://localhost/test")
.withUserConfiguration(ConfigurationWithClientMongoClient.class)
.run((context) -> assertThatExceptionOfType(
NoSuchBeanDefinitionException.class)
.isThrownBy(() -> context.getBean(MongoClient.class)));
}
@Configuration @Configuration
static class OptionsConfig { static class OptionsConfig {
@ -104,4 +118,13 @@ public class MongoAutoConfigurationTests {
} }
static class ConfigurationWithClientMongoClient {
@Bean
com.mongodb.client.MongoClient mongoClient() {
return MongoClients.create();
}
}
} }