We have a mongo server hosted on mongo atlas . It is a M3 replica set and all the testing in done on the replica set only.
My app configuration is as follows.
public #Bean MongoClient mongoClient() {
String userName = env.getProperty("spring.data.mongodb.username");
String password = env.getProperty("spring.data.mongodb.password");
String authDatabase = env.getProperty("spring.data.mongodb.authentication-database");
String uriString = env.getProperty("spring.data.mongodb.uri");
uri = new ConnectionString(uriString);
MongoCredential credential = MongoCredential.createCredential(userName, authDatabase, password.toCharArray());
return MongoClients
.create(MongoClientSettings.builder().credential(credential).applyConnectionString(uri).build());
}
public #Bean MongoTemplate mongoTemplate() {
String database = env.getProperty("spring.data.mongodb.database");
MongoTemplate template = new MongoTemplate(mongoClient(), database);
MappingMongoConverter mongoMapping = (MappingMongoConverter) template.getConverter();
mongoMapping.afterPropertiesSet();
template.setSessionSynchronization(SessionSynchronization.ALWAYS);
return template;
}
public #Bean MongoTransactionManager transactionManager(MongoDatabaseFactory dbFactory) {
return new MongoTransactionManager(dbFactory);
}
Now when we intiating a multi document transaction it is not rolling back .
#Transactional
public void createMultiDocument {
flag =true;
template.save1(doc1);
template.save2(doc2);
if(flag) {
throw new RuntimeException();
}
template.save3(doc3);
}
In the above operation document1 and document2 are saving to the database.
Sorry false alarm.
We are throwing a checked exception which does not ensure a rollback.
Related
I need to implement Spring boot - MongoDb application where There are 2 mongo DBs which have exact same database name & collections. Based on User making a request, i need to choose whether to fetch data from DB1 or DB2 (only difference in mongo URI host - IP).
E.g. I need some way to create 2 mongoTemplates like mTempA & mTempB in my Repository & based on some condition, use either of the template to execute query as below:
#Repository
public class MyCustomRepository {
private Logger logger = LoggerFactory.getLogger(MyCustomRepository.class);
#Autowired
private MongoTemplateA mongoTemplateA;// Need to know if this is possible & how
#Autowired
private MongoTemplateB mongoTemplateB;// Need to know if this is possible & how
public List<MyModel> findByCriteria(MyRequest request) {
List<MyModel> result;
//Query query = <build query based on request>
if (request.getUserType().equals("A")) {
result = mongoTemplateA.find(query, MyModel.class);
} else {
result = mongoTemplateB.find(query, MyModel.class);
}
logger.debug("Result fetched with {} records", result.size());
return result;
}
}
I don't want to have 2 separate Repo (Class or Interfaces) or different models to be used. Just want to have 2 different mongoTemplates to be injected in single repo.
Is this possible? If yes, please give some example code.
I have followed below tutorial:
https://dzone.com/articles/multiple-mongodb-connectors-with-spring-boot
As rightly pointed out by #Lucia, below is how it can be done:
Have 2 different configuration placeholders
#Configuration
#EnableMongoRepositories(basePackages = "com.snk.repository", mongoTemplateRef = "mongoTemplateA")
public class MongoConfigA {
// Configuration class for DB 1 access
}
#Configuration
#EnableMongoRepositories(basePackages = "com.snk.repository", mongoTemplateRef = "mongoTemplateB")
public class MongoConfigB {
// Configuration class for DB 2 access
}
Get one class which will help in reading custom properties for mongo db properties in application.properties:
#ConfigurationProperties(prefix = "mongodb")
public class MultipleMongoProperties {
private MongoProperties adb = new MongoProperties();
private MongoProperties bdb = new MongoProperties();
public MongoProperties getAdb() {
return adb;
}
public MongoProperties getBdb() {
return bdb;
}
}
Add a configuration class to create mongoTemplates:
#Configuration
#EnableConfigurationProperties(MultipleMongoProperties.class)
public class MultipleMongoConfig {
#Autowired
private MultipleMongoProperties mongoProperties = new MultipleMongoProperties();
#Bean(name = "mongoTemplateA")
#Primary
public MongoTemplate mongoTemplateA() {
return new MongoTemplate(aDbFactory(this.mongoProperties.getAdb()));
}
#Bean(name = "mongoTemplateB")
public MongoTemplate mongoTemplateB() {
return new MongoTemplate(bDbFactory(this.mongoProperties.getBdb()));
}
#Bean
#Primary
public MongoDbFactory aDbFactory(final MongoProperties mongo) {
return new SimpleMongoDbFactory(new MongoClientURI(mongo.getUri()));
}
#Bean
public MongoDbFactory bDbFactory(final MongoProperties mongo) {
return new SimpleMongoDbFactory(new MongoClientURI(mongo.getUri()));
}
}
Add below decelerations to your service/repository:
#Autowired
#Qualifier("mongoTemplateA")
private MongoTemplate mongoTemplateA;
#Autowired
#Qualifier("MongoTemplateB")
private MongoTemplate MongoTemplateB;
Add below properties in your application.properties:
mongodb.adb.uri=mongodb://user:pass#myhost1:27017/adb
mongodb.bdb.uri=mongodb://user:pass#myhost2:27017/bdb
If you have mongo rplica set, URL can be set as:
mongodb.adb.uri=mongodb://user:pass#myhost1,myhost2,myhost13/adb?replicaSet=rsName
mongodb.bdb.uri=mongodb://user:pass#myhost1,myhost2,myhost13/bdb?replicaSet=rsName
Based on your logic, use either of the template.
Thought, there are few catches:
Notice the #Primary annotation, one bean needs to be marked as primary. I haven't find any solution if no template is marked primary.
If any of the mongo DB is down & application is started/restarted, application will not start/deploy. to avoid this, #Autowired needs to be changed to #Autowired(required = false).
If any of the mongo DB is down & application is already running, it automatically uses 2nd mongo BD (which is not down). So, even if you want to use A DB, if it's down, requests are processed with B DB & vice-versa.
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");
}
}
When Mongodb is down, the spring boot application is down. I wish to handle exception of connectiontimeout and log the error without stopping application.
When finding an item from database is failed because the connection is not possible, the application should do another treatment like calling web service to find data.
Did you have any idea about this ?
Configuration
spring.data.mongodb.uri=mongodb://${MONGODB_DB_HOST}:${MONGODB_DB_PORT}/${MONGODB_DB_DATABASE}?connectTimeoutMS=${mongodb.connection.timeout}
I have used below code to configure mongodb connection in spring boot
you can specify socket timeout and connection timeout according to your need.
#Configuration
public class DatabaseConfiguration {
private static final Logger LOGGER = LoggerFactory.getLogger(DatabaseConfiguration.class);
#Value("${spring.data.mongodb.uri}")
private String mongoUri;
#Value("${spring.data.mongodb.database}")
private String mongoDbName;
#Value("${spring.data.mongodb.host}")
private String host;
#Value("${spring.data.mongodb.port}")
private int port;
#Value("${spring.data.mongodb.username}")
private String username;
#Value("${spring.data.mongodb.password}")
private String password;
#Bean
public MongoTemplate mongoTemplate() {
LOGGER.debug(" instantiating MongoDbFactory ");
SimpleMongoDbFactory mongoDbFactory = new SimpleMongoDbFactory(mongoClient(), mongoDbName);
MongoTemplate mongoTemplate = new MongoTemplate(mongoDbFactory);
return mongoTemplate;
}
#Bean
public MongoClient mongoClient() {
List<ServerAddress> servers = new ArrayList<ServerAddress>();
servers.add(new ServerAddress(host, port));
MongoClientOptions mongoClientOptions = MongoClientOptions.builder()
.connectionsPerHost(10)
.socketTimeout(2000)
.connectTimeout(2000)
.build();
if (Utils.isNotEmpty(username) && Utils.isNotEmpty(password)) {
List<MongoCredential> creds = new ArrayList<MongoCredential>();
creds.add(MongoCredential.createCredential(username, mongoDbName, password.toCharArray()));
return new MongoClient(servers,creds, mongoClientOptions);
} else
return new MongoClient(servers, mongoClientOptions);
}
#Bean
public MongoClientURI mongoClientURI() {
LOGGER.debug(" creating connection with mongodb with uri [{}] ", mongoUri);
return new MongoClientURI(mongoUri);
}
}
Define below properties in your application.yml file
mongodb specific properties
spring:
data:
mongodb:
database: dbname
host: localhost
port: 27017
username: dbusername
password: dbpassword
You can use the below sample example to configure mongodb timeout. I hope it's easy to convert to bean annonation for springboot. Else you can import the resource bean (#ImportResource)
<beans>
<mongo:mongo host="localhost" port="27017">
<mongo:options connections-per-host="8"
threads-allowed-to-block-for-connection-multiplier="4"
connect-timeout="1000"
max-wait-time="1500}"
auto-connect-retry="true"
socket-keep-alive="true"
socket-timeout="1500"
slave-ok="true"
write-number="1"
write-timeout="0"
write-fsync="true"/>
</mongo:mongo/>
</beans>
I am using spring-data mongo repositories and spring boot in my application. For a single database, I add the db configuration in application.properties. If i need to add another database, how do I add it? And how do I tell spring which data models / repositories are for which database?
thanks!
Here is a sample.
//This is like a base class for all your mongoconfigs.
public class MongoConfig {
private static final Logger LOG = LoggerFactory.getLogger(MongoConfig.class);
#NoArgsConstructor
#Getter
#Setter
class MongoDbProperties{
private String host;
private int port;
private String dbName;
private String dbUser;
private String dbPwd;
}
Mongo createMongoClient(ServerAddress serverAddress, MongoCredential... credentials) {
if (credentials != null && credentials.length > 0) {
final List<MongoCredential> credentialList = new ArrayList<>();
credentialList.addAll(Arrays.asList(credentials));
LOG.info("Creating connection with credentials: " + credentialList);
return new MongoClient(serverAddress, credentialList;
} else {
return new MongoClient(serverAddress);
}
}
Mongo getMongoClient(MongoDbProperties mongoDbProperties) {
LOG.info("Initializing mongo client for {}. Host={}, Port={} ", mongoDbProperties.getDbName(), mongoDbProperties.getHost(), mongoDbProperties.getPort());
Mongo mongoClient;
if(!mongoDbProperties.getDbUser().isEmpty() && !mongoDbProperties.getDbPwd().isEmpty()) {
MongoCredential credential = MongoCredential.createCredential(mongoDbProperties.getDbUser(), mongoDbProperties.getDbName(), mongoDbProperties.getDbPwd().toCharArray());
mongoClient = createMongoClient(new ServerAddress(mongoDbProperties.getHost(), mongoDbProperties.getPort()), credential);
}
else
mongoClient = createMongoClient(new ServerAddress(mongoDbProperties.getHost(), mongoDbProperties.getPort()));
LOG.info("Initializing mongo template for products dataBase={} ", mongoDbProperties.getDbName());
return mongoClient;
}
#Bean
#ConfigurationProperties(prefix = "mongo.default")
public MongoOptionsProperties defaultMongoOptionsProperties() {
return new MongoOptionsProperties();
}
}
Then have as many config files as mongo databases you want to connect to like these:
#Configuration
#EnableMongoRepositories(basePackages = "com.product.repository.shop.swiss", mongoTemplateRef = "swissMongoProductTemplate")
class SWissProductMongoConfig extends MongoConfig {
#Bean(name = "swissMongoProductTemplate")
public MongoTemplate swissMongoProductTemplate() throws Exception {
MongoDbProperties mongoDbProperties = shopSWissProductMongoProperties();
Mongo mongoClient = getMongoClient(mongoDbProperties);
return new MongoTemplate(mongoClient, mongoDbProperties.getDbName());
}
#Bean
#ConfigurationProperties(prefix = "mongo.product.swiss")
public MongoDbProperties shopSWissProductMongoProperties(){
return new MongoDbProperties();
}
}
and
#Configuration
#EnableMongoRepositories(basePackages = "com.product.repository.shop.france", mongoTemplateRef = "franceMongoProductTemplate")
class SWissProductMongoConfig extends MongoConfig {
#Bean(name = "franceMongoProductTemplate")
public MongoTemplate franceMongoProductTemplate() throws Exception {
MongoDbProperties mongoDbProperties = shopFranceProductMongoProperties();
Mongo mongoClient = getMongoClient(mongoDbProperties);
return new MongoTemplate(mongoClient, mongoDbProperties.getDbName());
}
#Bean
#ConfigurationProperties(prefix = "mongo.product.france")
public MongoDbProperties shopFranceProductMongoProperties() {
return new MongoDbProperties();
}
}
Have something like this in your yml
mongo:
product:
france :
dbName:
host:
port:
dbUser:
dbPwd:
swiss:
dbName:
host:
port:
dbUser:
dbPwd:
Make sure you have the repository classes in the packages mentioned in the basePackages attribute of annotaion EnableMongoRepositories
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());
}
}