I was reading through this Git issue:
https://github.com/spring-projects/spring-boot/issues/7589
with regards to Java Spring boot and am trying to figure out a way to bypass the crash upon startup.
The short version is that If you include the code for creating the mongo client:
#Bean
public MongoOperations mongoOperations() {
try {
//This runs an operation which uses my credentials to login to the db
return new MongoTemplate(mongoDbFactory());
} catch (Exception e){
e.printStackTrace();
return null;
}
}
and the MongoDB is running, it will connect and not have any problems, but if the MongoDB is not running, Spring will retry and after failing again, will crash and stop all startup sequences.
My question is this: is there a way to bypass this initial crash / check against the DB being up and running other than commenting out all code referencing it? Can I catch an exception somewhere low-level and let it pass through?
If your application behaves in such a way that MongoDB is optional, you have several options.
If you are migrating an existing application, the easiest from a start would be to exclude the auto-configuration and create the infrastructure yourself. Not in the way you've indicated as returning null from a #Bean method is quite nasty. Rather you could have some service that could lazily create the client and you could update your optional usages of MongoDB to go through that service. The service would be created regardless but would only create the underlying infrastructure if necessary.
The other option is to use a profile. If the main use case is that MongoDB is available then create a application-nomongo.properties (something like that) where you would exclude the auto-configuration using the spring.autoconfigure.exclude property. When the application starts without mongo, you can enable the nomongo profile and the auto-configuration will backoff. When it's not enabled, the Mongo bean will be created by Spring Boot.
I had the same issue. I want my application to be able to run offline as well, queue stuff and from time to time flush to database. This means it needs to be able to launch offline and connect whenever internet is available. Ofc, Spring crashed my app when it did not succeed in connecting.
What worked for me was this:
#Configuration
#EnableJpaRepositories(bootstrapMode = BootstrapMode.LAZY)
public class DataSourceConfig {
#Bean(destroyMethod = "close")
public DataSource dataSource() {
HikariConfig hikariConfig = new HikariConfig();
hikariConfig.setDriverClassName("xxx");
hikariConfig.setJdbcUrl("xxx");
hikariConfig.setUsername("xxx");
hikariConfig.setPassword("xxx");
hikariConfig.setConnectionTestQuery("SELECT 1");
hikariConfig.setPoolName("springHikariCP");
hikariConfig.setInitializationFailTimeout(-1);
hikariConfig.addDataSourceProperty("dataSource.cachePrepStmts", "true");
hikariConfig.addDataSourceProperty("dataSource.prepStmtCacheSize", "250");
hikariConfig.addDataSourceProperty("dataSource.prepStmtCacheSqlLimit", "2048");
hikariConfig.addDataSourceProperty("dataSource.useServerPrepStmts", "true");
HikariDataSource dataSource = new HikariDataSource(hikariConfig);
return dataSource;
}
#Bean
public EntityManager entityManager() {
return entityManagerFactory().getObject().createEntityManager();
}
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSource());
em.setPackagesToScan("blabla");
em.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
em.setJpaProperties(additionalProperties());
return em;
}
private Properties additionalProperties() {
Properties properties = new Properties();
properties.setProperty("hibernate.dialect", "org.hibernate.dialect.MySQL55Dialect");
return properties;
}
}
Also, place this in your App class
#SpringBootApplication(exclude = HibernateJpaAutoConfiguration.class)
application.yml looks like this:
#Spring properties
spring:
jpa:
hibernate:
ddl-auto: update
datasource:
url: "xxx"
username: xxx
password: xxx
driver-class-name: com.mysql.cj.jdbc.Driver
continue-on-error: true
hikari:
connection-timeout: 2000
initialization-fail-timeout: -1
auto-commit: true
This works for me, hopefully it will work for you guys as well, I saw many posts around here searching for a solution to this. Good luck!
Related
I am currently working on a project which is using Redis mainly for caching purposes, I am using Oracle as the main database and Spring Data JPA to handle the database layer in my project. I need to know how to use #Transactional annotation support to handle transactions in Redis. I have already referred to lots of tutorials and documentation regarding this scenario. In most of those tutorials, there is always the same set of source codes available. But still, I didn't have a clear idea about the implementation. Because in my application there is already a data source available which I configured through property file. (Oracle database) So I doubt the implementation of the dataSource bean. And I couldn't understand the usage of transactionManager bean too. How should I implement this properly please give a detailed explanation.
Source code which I found on the internet.
#Configuration
#EnableTransactionManagement // 1
public class RedisConfig {
#Bean
public StringRedisTemplate stringRedisTemplate(LettuceConnectionFactory redisConnectionFactory) {
// Configure redisTemplate
StringRedisTemplate stringRedisTemplate = new StringRedisTemplate();
stringRedisTemplate.setConnectionFactory(redisConnectionFactory);
/ / Open transaction support
stringRedisTemplate.setEnableTransactionSupport(true); // 2
return stringRedisTemplate;
}
#Bean
public PlatformTransactionManager transactionManager() throws SQLException {
return new DataSourceTransactionManager(dataSource()); // 3
}
#Bean
public DataSource dataSource() throws SQLException {
// ...
}
}
Updated :
Currently configured datasource properties in apppication.properties file.
# OracleDB connection settings
spring.datasource.url=jdbc:oracle:thin:#192.168.20.108:1521:orcl
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=oracle.jdbc.driver.OracleDriver
# HikariCP settings
spring.datasource.hikari.minimumIdle=5
spring.datasource.hikari.maximumPoolSize=20
spring.datasource.hikari.idleTimeout=30000
spring.datasource.hikari.maxLifetime=2000000
spring.datasource.hikari.connectionTimeout=30000
spring.datasource.hikari.poolName=redis-sample-pool
Resource about the Redis Transactions handling
There're two ways to configure DataSource
Using the config file
spring.datasource.url= jdbc:oracle:thin:#//localhost
spring.datasource.username=test
spring.datasource.password=test1234
spring.datasource.driver-class-name=oracle.jdbc.OracleDriver
#hibernate config dialect for JPA, choose dialect based on the db version
spring.jpa.database-platform=org.hibernate.dialect.Oracle10gDialect
You can create manually the same by reading these properties, with the help of DataSourceBuilder
#Bean
public DataSource dataSource(){
DataSourceBuilder dataSourceBuilder = DataSourceBuilder.create();
dataSourceBuilder.driverClassName("oracle.jdbc.OracleDriver");
dataSourceBuilder.url("jdbc:h2:mem:test");
dataSourceBuilder.username("test");
dataSourceBuilder.password("test1234");
return dataSourceBuilder.build();
}
Use case
Our spring-boot-backend (v 2.3.1) using a postgres-database with HikariCP as connection pool.
The application is online and a admin accidentally kills the database.
Expected behavior
Spring notice that the connection to DB was lost. The application will gracefully shutdown.
Actual behavior
Spring is still running. All methods, which needs the db, ending up in exceptions.
To recover a complete good state, we need manually to restart spring.
Notes
We have a lot of async workers and they cannot recover correctly, when the database is going back online.
And docker/kubernetes will notice when the application shutdown and can automatically restart it.
Question
How can I reach the expected behavior?
Sadly I found nothing similar in the web.
With the hints from #Zubair I build a new small solution.
Iam using the Spring-Actuator-package, because they have some ready classes for this use-case.
All what I need was a HealthIndicator-Bean, like this
#Bean
public HealthIndicator dataSourceHealthIndicator(final DataSource dataSource) {
return new DataSourceHealthIndicator(dataSource, "SELECT 1;");
}
and scheduled watcher (because HikariCP nor the HealthIndicators has any events).
#Scheduled(fixedRate = 10000L)
public void checkDBHealth() {
final Status status = this.dataSourceHealthIndicator.health().getStatus();
if (!Status.UP.equals(status)) {
log.error("DATABASE IS OFFLINE! SHUTTING DOWN!");
System.exit(1);
}
}
I hope that will be useful for others.
Edit
I had to change the config of HikariCP. Otherwise the health-checker waiting almost endless for database-connection.
#Bean
public DataSource dataSource() {
final HikariConfig config = new HikariConfig();
// default settings with user, password, jdbc, ...
config.setInitializationFailTimeout(1000);
config.setConnectionTimeout(1000);
config.setValidationTimeout(1000);
return new HikariDataSource(config);
}
If its spring 2.0, you may call the shut-down actuator from some monitoring service.
You can disable the actuator default provided health indicators in your property files and replace your custom DatasourceHealthIndicator by register it as bean.
#Bean
public DataSourceHealthIndicator dataSourceHealthIndicator(){
return new DataSourceHealthIndicator(dataSource, "SELECT 1");
}
#Component
#RequiredArgsConstructor
public class CustomHealth implements HealthIndicator {
private final DataSourceHealthIndicator healthIndicator;
#Override
public Health health() {
// ...
return Health.status(healthIndicator.health().getStatus()).build();
}
}
And set your properties like this.
application.yaml
management:
health:
db:
enabled: false
application.properties
management.health.db.enabled: false
There is Spring Boot Aplication. For forking with DB I use Spring Data (JPA + Repository).
Is there way to change DB user-credentials for datasource per API user-request? I mean each MVC request must do his own work with exactly his DB user-credentials.
I've read info about AbstractRoutingDataSource. But there is limitation: there isn't possibility to add datasource at runtime in DataSourceMap.
In fact I want to use apache PerUserPoolDataSource and change DB user-credentials (using Spring Security Context to get user information)
I've found solution. First of all need to define custom datasource configuration.
#Bean
public UserCredentialsDataSourceAdapter dataSource() throws ClassNotFoundException {
final UserCredentialsDataSourceAdapter dataSourceAdapter = new UserCredentialsDataSourceAdapter();
dataSourceAdapter.setTargetDataSource(perUserPoolDataSource());
return dataSourceAdapter;
}
private PerUserPoolDataSource perUserPoolDataSource() throws ClassNotFoundException {
DriverAdapterCPDS driverAdapter = new DriverAdapterCPDS();
driverAdapter.setDriver(properties.getDriverClassName());
driverAdapter.setUrl(properties.getUrl());
driverAdapter.setUser(properties.getUsername());//default
driverAdapter.setPassword(properties.getPassword());//default
driverAdapter.setAccessToUnderlyingConnectionAllowed(true);
PerUserPoolDataSource dataSource = new PerUserPoolDataSource();
dataSource.setConnectionPoolDataSource(driverAdapter);
return dataSource;
}
As you see - there is base datasource from Apache: PerUserPoolDataSource.
Also as main datasource I will use UserCredentialsDataSourceAdapter.
Then need to write custom filter, which will change user credentials for successefully authenticated users:
dataSourceAdapter.setCredentialsForCurrentThread(user.getUsername(), user.getPassword());
This solution works for blocked paradygm (not reactive). One thread with credentials for specific user will do all queries.
i have issue with disabling specific DataSources in Spring Actuator. I currently have task in my application to implement Spring Actuator, but need Actuator to ignore/disable for some features in app(Health Indicator mainly). Application is built from other mini apps. Any suggestions or instructions how to start it ?
Disable the default datasources health indicator
management.health.db.enabled=false
and customise the your required data source with DataSourceHealthIndicator
example:
#Autowired
private DataSource requiredDataSource;
#Bean
public DataSourceHealthIndicator requiredDataSourceHealthIndicator() {
return new DataSourceHealthIndicator(requiredDataSource);
}
An important point to prevent the Health check system becomes fully none-functional due to infinite loop for waiting for database connection is to configure the connectionTimeout and validationTimeout in datasource config.
In the case that we using HikariCP as connection pool provider, with respect to the case in hand the implementation will look like below,
HikariConfig config = new HikariConfig();
...
config.setInitializationFailTimeout(1000);
config.setConnectionTimeout(1500);
config.setValidationTimeout(1500);
...
return new HikariDataSource(config).unwrap(DataSource.class)
#Bean
public DataSourceHealthIndicator dataSourceHealthIndicator(){
...
return new DataSourceHealthIndicator(dataSource, "SELECT 1");
}
#Component
#RequiredArgsConstructor
public class CustomHealth implements HealthIndicator {
#Override
public Health health() {
...
return Health.status(healthIndicator.health().getStatus()).build();
}
private final DataSourceHealthIndicator healthIndicator;
}
And as Bhushan Uniyal stated you can disable the actuator default DataSourceHealthIndicator in your property files
YML
management:
health:
db:
enabled: false
Properties
management.health.db.enabled: false
The problem is simple when I use Spring boot 1.5.2 with Hibernate to connect to a datasource with configued like this:
#Bean
public DataSource dataSource() {
DataSourceBuilder = datasourceBuilder = DataSourceBuilder.create();
dataSourceBuilder.url(DATASOURCE_URL);
dataSourceBuilder.username(DATASOURCE_USERNAME);
dataSourceBuilder.password(DATASOURCE_PASSWORD);
return dataSourceBuilder.build();
}
and this is a Command line application, so when the application is almost done, I just want to close the Hibernate connections to the database and rename the database with JDBC. However, I don't know how to do this in Spring boot, any idea?
I tried to inject the DataSource object to a class to rename database but it cannot close the connections to database.
#Autowired
private DataSource dataSource;
public void closeConnection() {
dataSource.close();
}
with error
org.postgresql.util.PSQLException: ERROR: database "DATABASE" is being accessed by other users
Detail: There are 10 other sessions using the database.
You can use pg_terminate_backend() to kill a connection. You have to be superuser to use this function. This works on all operating systems the same.
refer this stackoverflow link :
https://stackoverflow.com/a/5109190/7801800