Spring boot: Handling mongodb timeoutException - mongodb

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>

Related

Mongo db transaction not rolling back spring boot

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.

Spring Boot gives an error of URL must start with 'jdbc'

I am trying to configure a Postgres database in spring boot using annotation configuration. I have all the database credentials in a file named database.properties and the configuration file is called DBconfig.java
database.url= jdbc:postgresql://localhost/mydb
database.driverClassName= com.postgresql.jdbc.Driver
database.username= postgres
database.password= password
The dbConfig file -
#Configuration
#PropertySource("classpath:databaseAccess/database.properties")
public class DBconfig {
#Value("${username}")
private String username;
#Value("${password}")
private String password;
#Value("${url}")
private String url;
#Bean
#Qualifier("postgresDB")
public DataSource dataSource() {
DataSourceBuilder dataSource = DataSourceBuilder.create();
dataSource.url(url);
dataSource.password(password);
//dataSource.driverClassName(driverClassName);
dataSource.username(username);
return dataSource.build();
}
#Bean
#Qualifier("jdbcTemplate")
public JdbcTemplate jdbcTemplate() {
return new JdbcTemplate(dataSource());
}
}
This is my main file
#SpringBootApplication
public class GetNotificationsApplication {
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(DBconfig.class);
JdbcTemplate template= ctx.getBean("jdbcTemplate", JdbcTemplate.class);
template.execute("CREATE TABLE TEST( test VARCHAR(20))");
}
}
I keep getting the error
Factory method 'dataSource' threw exception; nested exception is java.lang.IllegalArgumentException: URL must start with 'jdbc'
Try to change value for url parameter by defining port number for postgres. Assuming that postgres is running on 5432 which is the default port.
Change
jdbc:postgresql://localhost/mydb
To
jdbc:postgresql://localhost:5432/mydb
For port number check Find the host name and port using PSQL commands
UPDATE:
Also change #Value("${username}") to #Value("${database.username}") and other properties too by prefix database.

How to make `org.mongodb.driver.cluster` use embedded mongodb within spring boot?

Trying to use embedded mongodb for my spring local profile. Here is the configuration for MongoTemplate
#Configuration
#Profile("local")
public class LocalMongoConfig {
private static final String MONGO_DB_URL = "localhost";
private static final String MONGO_DB_NAME = "embeded_db";
#Bean
#Primary
public MongoTemplate mongoTemplate() throws IOException {
EmbeddedMongoFactoryBean mongo = new EmbeddedMongoFactoryBean();
mongo.setBindIp(MONGO_DB_URL);
MongoClient mongoClient = mongo.getObject();
MongoTemplate mongoTemplate = new MongoTemplate(mongoClient, MONGO_DB_NAME);
return mongoTemplate;
}
}
And this is application-local.yml
spring:
data:
mongodb:
uri: mongodb://127.0.0.1:27017/embeded_db
But when running the application from the logs I can see that embedded mongodb is starting at random ports.
13:53:49.849 [Thread-7] INFO org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongo - 2020-03-20T13:53:49.849+0800 I NETWORK [thread1] waiting for connections on port 51564
Then I get following error saying connection refused which is correct because it tries to connect to different port(27017)
13:53:50.953 [restartedMain] INFO org.mongodb.driver.cluster - Cluster description not yet available. Waiting for 30000 ms before timing out
13:53:51.639 [cluster-ClusterId{value='5e745a6e973cbd4bd45d073e', description='null'}-127.0.0.1:27017] INFO org.mongodb.driver.cluster - Exception in monitor thread while connecting to server 127.0.0.1:27017
com.mongodb.MongoSocketOpenException: Exception opening socket
at com.mongodb.internal.connection.SocketStream.open(SocketStream.java:67)
at com.mongodb.internal.connection.InternalStreamConnection.open(InternalStreamConnection.java:126)
at com.mongodb.internal.connection.DefaultServerMonitor$ServerMonitorRunnable.run(DefaultServerMonitor.java:117)
at java.lang.Thread.run(Thread.java:748)
Caused by: java.net.ConnectException: Connection refused: connect
How can I make sure mongodb.driver use embedded mongodb?
There is a method at EmbeddedMongoFactoryBean use it to set desired port.
/**
* The port MongoDB should run on. When no port is provided, then some free
* server port is automatically assigned. The value must be between 0 and 65535.
*/
public void setPort(int port) {
builder.port(port);
}
Try this:
#Bean
#Primary
public MongoTemplate mongoTemplate() throws IOException {
EmbeddedMongoFactoryBean mongo = new EmbeddedMongoFactoryBean();
mongo.setPort(27017); //here
mongo.setBindIp(MONGO_DB_URL);
MongoClient mongoClient = mongo.getObject();
MongoTemplate mongoTemplate = new MongoTemplate(mongoClient, MONGO_DB_NAME);
return mongoTemplate;
}
But according the docs you should do this:
#Bean(destroyMethod = "close")
#Profile("local")
MongoClient mongo(){
return new EmbeddedMongoBuilder().port().bindIp().build();
}

how do i connect to multiple databases using spring boot and spring-data for mongodb

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

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());
}
}