Springboot MongoDB connection URI is specifed in other key (not using spring.data.mongodb.uri) - mongodb

For my Springboot application, I have a requirement that MongoDB URI should be specified with "app1.mongodb.uri" in application.properties. Yes we don't want to use "spring.data.mongodb.uri" because I was told that it's misleading (what!?). Does anyone know what is the simplest way to do that ? My application is all running fine, and I'm so reluctant to make any big change because of this "requirement".

Figured it out how to do it. The trick is to override the beam MongoClient and Mongotemplate.
import com.mongodb.MongoClient;
import com.mongodb.MongoClientURI;
#SuppressWarnings("deprecation")
#Configuration
#EnableMongoRepositories
#PropertySource("classpath:application.properties")
public class MongoDBConfig extends AbstractMongoConfiguration {
#Value("${app1.mongodb.db}")
private String database;
#Value("${app1.mongodb.uri}")
private String uri;
#Override
#Bean
public MongoClient mongoClient() {
MongoClientURI mongoURI = new MongoClientURI(uri);
MongoClient client = new MongoClient(mongoURI);
return client;
}
#Override
protected String getDatabaseName() {
return database;
}
#Bean
public MongoTemplate mongoTemplate() throws Exception {
return new MongoTemplate(mongoClient(), getDatabaseName());
}
}

Related

Make MongoDB Spring Boot test use existing configuration and embedded test database

this is my existing MongoDB configuration:
#Configuration
public class MongoConfig extends AbstractMongoClientConfiguration {
#Autowired
MongoDBProperties mongoDBProperties;
#Override
public MongoClient mongoClient() {
ConnectionString connectionString = new ConnectionString(mongoDBProperties.getUrl());
MongoClientSettings mongoClientSettings = MongoClientSettings.builder()
.applyConnectionString(connectionString)
.build();
return MongoClients.create(mongoClientSettings);
}
#Override
public MongoCustomConversions customConversions() {
return new MongoCustomConversions(Arrays.asList(new OffsetDateTimeReadConverter(), new OffsetDateTimeWriteConverter()));
}
#Override
protected String getDatabaseName() {
return mongoDBProperties.getDatabase();
}
}
I need the converters because the document that I store contains OffsetDataTime fields.
I want to test saving a document. I have included this dependency:
<dependency>
<groupId>de.flapdoodle.embed</groupId>
<artifactId>de.flapdoodle.embed.mongo</artifactId>
<scope>test</scope>
</dependency>
My test looks like this:
#DataMongoTest
public class VersionRepositoryTestCase {
#Autowired
MyDocumentRepository cut;
#Autowired
MongoDBProperties mongoDBProperties;
#Test
public void speichernVonVersionMitVersionInfoFunktioniert() {
MyDocument myDocument = createMyDocument();
cut.save(versionDocument);
}
}
My problem is: if I use #SpringBootTest, it is not the embedded MongoDBD that is being used. if I use the #DataMongoTest and override the MongoDbProperties bean, the converters are not used so the test fails. So how can I use my config class with the embedded test mongo db instance?
It works with #Import anotation above the Test class.

Spring Data Entity UUID is stored as legacy UUID in MongoDB

I'm dumping some data into my MongoDb and generate a UUID on the way. In the collection, this UUID field is stored as LUUID (legacy UUID - type 3) and I don't know how to avoid this, because I would want the format to be the standard UUID (type 4).
Entity:
#Document(collection = "sms")
public class SmsEntity {
...
private UUID ubmMessageSid; // <- this field gets stored as LUUID
...
public static class Builder {
...
private UUID ubmMessageSid;
...
public Builder ubmMessageSid(UUID ubmMessageSid) {
this.ubmMessageSid = ubmMessageSid;
return this;
}
public SmsEntity build() {return new SmsEntity(this);}
}
}
Repo:
#Repository
public interface SmsRepository extends CrudRepository<SmsEntity, String> {
}
Service storing this entity:
...
var ubmId = UUID.randomUUID();
var smsEntity = SmsEntity.builder()
.ubmMessageSid(ubmId)
...
.build();
repository.save(smsEntity);
Anything I have to annotate or configure to store the UUID as Binary/type4?
In Spring Boot 2, for Spring MongoDB 3.x, you can set this using the autoconfiguration properties:
# Options: unspecified, standard, c_sharp_legacy, java_legacy, python_legacy
spring.data.mongodb.uuid-representation=standard
On version 3.x of Spring Data Mongo MongoClient has been replaced by MongoClientSettings:
CodecRegistry codecRegistry =
CodecRegistries.fromRegistries(CodecRegistries.fromCodecs(new UuidCodec(UuidRepresentation.JAVA_LEGACY)),
MongoClientSettings.getDefaultCodecRegistry());
return new MongoClient(new ServerAddress(address, port), MongoClientSettings.builder().codecRegistry(codecRegistry).build());
You can set the UUID codec in your Mongo config. That will persist your UUIDs with type 4 codec. The code you need to do that is the following:
CodecRegistries.fromRegistries(CodecRegistries.fromCodecs(new UuidCodec(UuidRepresentation.STANDARD)),
MongoClient.getDefaultCodecRegistry());
return new MongoClient(new ServerAddress(address, port), MongoClientOptions.builder().codecRegistry(codecRegistry).build());
Here is the complete class just in case:
import com.mongodb.MongoClient;
import com.mongodb.MongoClientOptions;
import com.mongodb.ServerAddress;
import org.bson.UuidRepresentation;
import org.bson.codecs.UuidCodec;
import org.bson.codecs.configuration.CodecRegistries;
import org.bson.codecs.configuration.CodecRegistry;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.config.AbstractMongoConfiguration;
import org.springframework.data.mongodb.core.mapping.event.ValidatingMongoEventListener;
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
#Configuration
#EnableAutoConfiguration
#EnableMongoRepositories(basePackages = "com.yourpackage.repositories")
public class MongoConfig extends AbstractMongoConfiguration {
#Value("${mongo.database}")
String database;
#Value("${mongo.address}")
String address;
#Value("${mongo.port}")
Integer port;
#Override
protected String getDatabaseName() {
return database;
}
#Override
public MongoClient mongoClient() {
CodecRegistry codecRegistry =
CodecRegistries.fromRegistries(CodecRegistries.fromCodecs(new UuidCodec(UuidRepresentation.STANDARD)),
MongoClient.getDefaultCodecRegistry());
return new MongoClient(new ServerAddress(address, port), MongoClientOptions.builder().codecRegistry(codecRegistry).build());
}
#Bean
public LocalValidatorFactoryBean localValidatorFactoryBean() {
return new LocalValidatorFactoryBean();
}
#Bean
public ValidatingMongoEventListener validatingMongoEventListener() {
return new ValidatingMongoEventListener(localValidatorFactoryBean());
}
}
Attention: With the new Spring Boot Version 2.2.0.RELEASE AbstractMongoConfiguration becomes deprecated and this is not working anymore. I made a post for that, maybe it's a bug or maybe someone knows the answer: Spring Boot Standard UUID codec not working with AbstractMongoClientConfiguration
In my case the property in the application.yml didn't work as well as the one using CodecRegistries. My working solution is:
MongoConfig.java:
#Bean
public MongoClient mongoClient() {
MongoClientSettings.Builder builder = MongoClientSettings.builder();
builder.uuidRepresentation(UuidRepresentation.JAVA_LEGACY);
return MongoClients.create(builder.build());
}

MongoTemplate Custom Config using Spring boot

I was looking to change WriteResultChecking property of mongoTemplate whilst working on Spring boot app (2.0.5). I found out a way via extending AbstractMongoConfiguration as below.
I got the same working, however i found this approach a bit risky.
Saying this because this approach forced me to write a implementation for
public MongoClient mongoClient() {
return new MongoClient(host, port);
}
Now MongoClient is the central class to maintain connections with MongoDB and if i am forced to write implementation for the same, then i may be possibly missing out on optimizations that spring framework does.
Can someone please suggest any other optimal way of overriding some properties/behaviours without having to tinker too much ?
#Configuration
public class MyMongoConfigs extends AbstractMongoConfiguration {
#Value("${spring.data.mongodb.database}")
private String databaseName;
#Value("${spring.data.mongodb.host}")
private String host;
#Value("${spring.data.mongodb.port}")
private int port;
#Override
public MongoClient mongoClient() {
return new MongoClient(host, port);
}
#Override
protected String getDatabaseName() {
return databaseName;
}
#Bean
public MongoTemplate mongoTemplate() throws Exception {
MongoTemplate myTemp = new MongoTemplate(mongoDbFactory(), mappingMongoConverter());
**myTemp.setWriteResultChecking(WriteResultChecking.EXCEPTION);**
return myTemp;
}
You are in right direction. Using AbstractMongoConfiguration you override the configs that you need to customize and it's the right way to do it. AbstractMongoConfiguration is still Spring Provided class, so the you don't have to worry about optimization, unless you mess with your configuration.
This is my approach:
package app.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.MongoDbFactory;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.WriteResultChecking;
import org.springframework.data.mongodb.core.convert.MongoConverter;
import com.mongodb.WriteConcern;
#Configuration
class ApplicationConfig {
#Bean
MongoTemplate mongoTemplate(MongoDbFactory mongoDbFactory, MongoConverter converter) {
MongoTemplate mongoTemplate = new MongoTemplate(mongoDbFactory, converter);
mongoTemplate.setWriteConcern(WriteConcern.MAJORITY);
mongoTemplate.setWriteResultChecking(WriteResultChecking.EXCEPTION);
return mongoTemplate;
}
}
I have figured this out by inspecting the source code of org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration.
I wrote CustomMongoTemplate which override method find() with added criteria to check if document has 'deletedAt' field;
Custom MongoTemplate:
public class CustomMongoTemplate extends MongoTemplate {
public CustomMongoTemplate(MongoDatabaseFactory mongoDbFactory, MongoConverter mongoConverter) {
super(mongoDbFactory, mongoConverter);
}
#Override
public <T> List<T> find(Query query, Class<T> entityClass, String collectionName) {
query.addCriteria(Criteria.where("deletedAt").exists(Boolean.FALSE));
return super.find(query, entityClass, collectionName);
}
Then create Bean in configuration class:
#Configuration
public class MyConfiguration {
//...
#Bean(name = "mongoTemplate")
CustomMongoTemplate customMongoTemplate(MongoDatabaseFactory databaseFactory, MappingMongoConverter converter) {
return new CustomMongoTemplate(databaseFactory, converter);
}
//...
}
And last thing - allow Spring to override default MongoTemplate bean. Add next thing to your application.properties file:
spring.main.allow-bean-definition-overriding=true

Spring Boot + MongoDB: How to reuse connections

I'm using SpringBoot + MongoDB. I created my object as follows.
I am able to #Autowrite the DocumentStoreConfig object in my Service/Controller and make calls to Mongo.
Sample call:
#Autowired
private DocumentStoreConfig docStoreConfig;
this.docStoreConfig.mongoClient().getDatabase("db_name").getCollection(collection).insertOne(doc);
Problem I see is that each call does a 'new' MongoClient and opens up a new connections.
What is the guidance on setting up a pool.. or reusing the same connection object rather than making the painful cost of opening a brand new connection.
#Configuration
public class DocumentStoreConfig extends AbstractMongoConfiguration {
#Value("${spring.data.mongodb.uri}")
private String connectionString;
#Value("${documentstore.database}")
private String databaseName;
#Override
public String getDatabaseName() {
return this.databaseName;
}
#Override
public MongoClient mongoClient() {
System.out.println("**** \n\n\n NEW MONGO \n\n\n");
return new MongoClient(new MongoClientURI(this.connectionString));
}
public MongoCollection<Document> getFailureCollection() {
return this.mongoClient().getDatabase(this.databaseName).getCollection("failure");
}
}

Connecting to MongoDB from spring boot app using ssl

im trying to connect my spring boot app to mongodb using ssl. I followed the steps described here, but they dont work for me.
https://www.compose.com/articles/how-to-connecting-to-compose-mongodb-with-java-and-ssl/
any idea?
Thanks Alem
I would suggest that you look at Accessing Data with MongoDB available here https://spring.io/guides/gs/accessing-data-mongodb/ for basic usage examples. spring-boot-starter-data-mongodb will get you a long way, what you need to do is configure a MongoClientOptions bean like this
#Bean
public MongoClientOptions mongoClientOptions(){
System.setProperty ("javax.net.ssl.keyStore","<<PATH TO KEYSTOR >>");
System.setProperty ("javax.net.ssl.keyStorePassword","PASSWORD");
MongoClientOptions.Builder builder = MongoClientOptions.builder();
MongoClientOptions options=builder.sslEnabled(true).build();
return options;
}
and pass the mongo client options to MongoClient instance as an argument as follows
public MongoClient(ServerAddress addr, MongoClientOptions options) {
super(addr, options);
}
Adding further, when mongo processs is started with
mongo --ssl --sslAllowInvalidCertificates --host --port
clients connecting to the mongo process dont have to set any options to support this.
I used this post Spring data mongodb, how to set SSL? and this spring.io guide as reference.
Hope that it helps
If you just want to connect your spring boot app with mongodb, you can use the keyStore and trustStore with java code. So you dont have to add your certificate via command line. If you are using cloud foundry you can connect your app with mongodbServices and then you have all the credentials you need in System.getEnv("VCAP_SERVICES").
#Configuration
public class MongoConfiguration extends AbstractMongoConfiguration {
private static Log logger = LogFactory.getLog(MongoConfiguration.class);
#Value("${spring.data.mongodb.database}")
private String defaultDatabase; //database you want to connect
private String host;
private int port;
private String authenticationDb; //usually admin
private String username;
private char[] password;
private String certificateDecoded; //your CA Certifcate decoded (starts with BEGIN CERTIFICATE)
public MongoConfiguration() {
//method for credentials initialization
}
//you can't set replicaset=replset in mongooptions so if you want set replicaset, you have to use
// customEditorConfigurer in combintaion with class that implementsPropertyEditorRegistrar
#Bean
public static CustomEditorConfigurer customEditorConfigurer(){
CustomEditorConfigurer configurer = new CustomEditorConfigurer();
configurer.setPropertyEditorRegistrars(
new PropertyEditorRegistrar[]{new ServerAddressPropertyEditorRegistrar()});
return configurer;
}
#Override
protected String getDatabaseName() {
return authenticationDb;
}
#Override
#Bean
public MongoClient mongoClient() {
MongoClient mongoClient = new MongoClient(Arrays.asList(new ServerAddress(host, port)), mongoCredentials(), mongoClientOptions());
return mongoClient;
}
#Bean
public MongoClientOptions mongoClientOptions() {
MongoClientOptions.Builder mongoClientOptions = MongoClientOptions.builder().sslInvalidHostNameAllowed(true).sslEnabled(true);
try {
InputStream inputStream = new ByteArrayInputStream(certificateDecoded.getBytes(StandardCharsets.UTF_8));
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
X509Certificate caCert = (X509Certificate) certificateFactory.generateCertificate(inputStream);
TrustManagerFactory trustManagerFactory = TrustManagerFactory
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null); // You don't need the KeyStore instance to come from a file.
keyStore.setCertificateEntry("caCert", caCert);
trustManagerFactory.init(keyStore);
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustManagerFactory.getTrustManagers(), null);
mongoClientOptions.sslContext(sslContext);
mongoClientOptions.sslInvalidHostNameAllowed(true);
} catch (Exception e) {
throw new IllegalStateException(e);
}
return mongoClientOptions.build();
}
private MongoCredential mongoCredentials() {
return MongoCredential.createCredential(username, authenticationDb, password);
}
//With MongoTemplate you have access to db.
#Bean
public MongoTemplate mongoTemplate() {
SimpleMongoDbFactory factory = new SimpleMongoDbFactory(mongoClient(), defaultDatabase);
return new MongoClient(factory);
}
}
public final class ServerAddressPropertyEditorRegistrar implements PropertyEditorRegistrar {
#Override
public void registerCustomEditors(PropertyEditorRegistry registry) {
registry.registerCustomEditor(ServerAddress[].class, new ServerAddressPropertyEditor());
}
}