Shutown Spring when DB is lost - java

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

Related

Spring Data. Change DB user credentials at runtime

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.

Spring #Conditional based on a value in database table

Condition evaluation depends on a value provided in data base table
#Component
public class XYZCondition implements Condition{
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
//based on value defined in DB should return true/false
}
}
As Condition is executing very early, unable to fetch db value
is there any alternate way to achieve this ?
Database values can be changed during application work, while it doesn't seem a good idea to reload application context.
So I would recommended using configuration properties to choose which beans should be available in context.
Moreover, there's a Spring Cloud Config that allows you to store configuration in git or some other storages. Its consumers may restart context once configuration changes.
It seems worth talking a look at it as well.
Well, you're trying to do something that doesn't map well to spring boot in this form.
I suggest to slightly change the requirement:
Instead of trying to access the database in the custom condition, create a custom source of configuration and load the property from the database into the Environment so that when the conditionals get evaluated later on during the startup process, the property with an associated value (previously resolved from the database) is already available.
Examples of following such an approach are:
- Spring boot cloud config that reads the configuration properties from "remote" config service (via REST)
- Spring boot consul integration (that reads from consul obviously)
This approach is much more spring-friendly, and also has can save the application from calling the database multiple times (what if you have 100 beans with this custom conditional) - will it do 100 queries?
Now, this will mean probably that you won't need a custom conditional - probably it will be #Condition on property.
Another caveat is that you won't be able to use JPA/Spring Data to load this property, probably you'll have to go with a Plain JDBC here.
Hmm, maybe you can just create a Configuration Class and Inject your Repository then create a Bean. Then inside the bean method fetch the value from the database and return conditional instance. something like this
#Configuration
public class Config {
#Autowired
private Repository repository;
#Bean
public Interface interface(){
boolean val = reposiory.getDBValue();
if(val)
return new Impl1();
else
return new Impl2();
}
}
sadly you cannot inject a lot into condition: so try with plain url and make manual connection to server,
but in my case this didn't work as I had flyway migration that was adding the same configuration value into the database (chicken and egg problem)
class EnableXXXCondition implements Condition {
private Environment environment;
#SneakyThrows
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
environment = context.getBeanFactory().getBean(Environment.class);
final String url = environment.getProperty("spring.datasource.url");
final String user = environment.getProperty("spring.datasource.username");
final String password = environment.getProperty("spring.datasource.password");
String x;
//we have problems with flyway before
try (Connection connection = DriverManager.getConnection(url, user, password)) {
try (Statement statement = connection.createStatement()) {
try (final ResultSet resultSet = statement.executeQuery("select x from y")) {
resultSet.next();
x = resultSet.getString("x");
}
}
}
return Boolean.valueOf(x);
}
}
You can try defining your condition in your configuration file and use it like below :
#ConditionalOnProperty(name="filter.enabled", havingValue="true")

Disabling HealtCheck for specific DataSources in Spring Actuator

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

How can I run Spring with MongoDB disabled, but still installed?

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!

Spring JdbcTemplate alter session

I want to alter Oracle session for every connection that I get from the connection pool.
I found that it can be done by simply execute a statement. See here.
Is there a way to hook into the jdbc template or the datasource and execute a statement after the connection pool creates a new connection.
I'm using Spring Boot and creating the datasource that way:
#Bean
#ConfigurationProperties(prefix="datasource.local")
public DataSource localDataSource() {
return DataSourceBuilder.create().build();
}
There are a lot of ways to do so.
The first one:
DataSource is an interface, so why don't you implement it yourself (use Proxy pattern)? Create something like this:
class MyDataSource implements DataSource {
private DataSource realDataSource;
public Connection getConnection() {
Connection c = realDataSource.getConnection();
// do whatever you want to do and
return c;
}
}
All other methods will delegate directly to realDataSource.
This proxy can be used in a provided code snippet.
You can use some AOP - just provide an advice that after get connection is created will run and do whatever you need there. Basically it's the same proxy but automatically created by Spring.

Categories