spring-projects / spring-boot

Spring Boot
https://spring.io/projects/spring-boot
Apache License 2.0
74.8k stars 40.6k forks source link

Allow an app to start when its DataSource is unable to connect to the DB #4779

Open dsyer opened 8 years ago

dsyer commented 8 years ago

As long as this doesn't make the code too complicated (possibly it can be done with AOP which is clean), I would say a retry would be best practice for 12factor apps - you shouldn't rely on the database being available on startup, but you can assume that it will come back eventually up to a point.

There are probably other places we could do this, but you have to start somewhere. For reference, we have an optional retry (driven by classpath) in spring-cloud-config for the config client connection to the config server. It's a similar issue.

wilkinsona commented 8 years ago

Another place: if you're using Spring Session backed by Redis, the app fails to start when Redis is down

jelmo commented 8 years ago

+1

dsyer commented 8 years ago

We decided we can prototype something based on the Spring Cloud config client code: it's opt in, and also only on if spring-retry is on the classpath. In that case if a slow connection fails we back off and wait. The Spring application context will delay startup, but that's probably better (more robust) than just failing. Essentially this is most useful at development time because in production there should be an external agent (docker compose, a PaaS, supervisord, monit, etc.) that restarts a failed process, so the cleanest thing to do is fail as fast as possible. So maybe the retry behaviour should be on by default if devtools are on, and off otherwise.

singram commented 8 years ago

+1 Defaults sound reasonable with option to override.

wilkinsona commented 7 years ago

Another example is Hibernate. It fails to build the entity manager when the database is down:

org.postgresql.util.PSQLException: Connection to localhost:5432 refused. Check that the hostname and port are correct and that the postmaster is accepting TCP/IP connections.
    at org.postgresql.core.v3.ConnectionFactoryImpl.openConnectionImpl(ConnectionFactoryImpl.java:262) ~[postgresql-9.4.1212.jre7.jar:9.4.1212.jre7]
    at org.postgresql.core.ConnectionFactory.openConnection(ConnectionFactory.java:51) ~[postgresql-9.4.1212.jre7.jar:9.4.1212.jre7]
    at org.postgresql.jdbc.PgConnection.<init>(PgConnection.java:215) ~[postgresql-9.4.1212.jre7.jar:9.4.1212.jre7]
    at org.postgresql.Driver.makeConnection(Driver.java:404) ~[postgresql-9.4.1212.jre7.jar:9.4.1212.jre7]
    at org.postgresql.Driver.connect(Driver.java:272) ~[postgresql-9.4.1212.jre7.jar:9.4.1212.jre7]
    at org.apache.tomcat.jdbc.pool.PooledConnection.connectUsingDriver(PooledConnection.java:310) ~[tomcat-jdbc-8.5.6.jar:na]
    at org.apache.tomcat.jdbc.pool.PooledConnection.connect(PooledConnection.java:203) ~[tomcat-jdbc-8.5.6.jar:na]
    at org.apache.tomcat.jdbc.pool.ConnectionPool.createConnection(ConnectionPool.java:718) [tomcat-jdbc-8.5.6.jar:na]
    at org.apache.tomcat.jdbc.pool.ConnectionPool.borrowConnection(ConnectionPool.java:650) [tomcat-jdbc-8.5.6.jar:na]
    at org.apache.tomcat.jdbc.pool.ConnectionPool.init(ConnectionPool.java:468) [tomcat-jdbc-8.5.6.jar:na]
    at org.apache.tomcat.jdbc.pool.ConnectionPool.<init>(ConnectionPool.java:143) [tomcat-jdbc-8.5.6.jar:na]
    at org.apache.tomcat.jdbc.pool.DataSourceProxy.pCreatePool(DataSourceProxy.java:118) [tomcat-jdbc-8.5.6.jar:na]
    at org.apache.tomcat.jdbc.pool.DataSourceProxy.createPool(DataSourceProxy.java:107) [tomcat-jdbc-8.5.6.jar:na]
    at org.apache.tomcat.jdbc.pool.DataSourceProxy.getConnection(DataSourceProxy.java:131) [tomcat-jdbc-8.5.6.jar:na]
    at org.hibernate.engine.jdbc.connections.internal.DatasourceConnectionProviderImpl.getConnection(DatasourceConnectionProviderImpl.java:122) [hibernate-core-5.0.11.Final.jar:5.0.11.Final]
    at org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator$ConnectionProviderJdbcConnectionAccess.obtainConnection(JdbcEnvironmentInitiator.java:180) [hibernate-core-5.0.11.Final.jar:5.0.11.Final]
    at org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator.initiateService(JdbcEnvironmentInitiator.java:68) [hibernate-core-5.0.11.Final.jar:5.0.11.Final]
    at org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator.initiateService(JdbcEnvironmentInitiator.java:35) [hibernate-core-5.0.11.Final.jar:5.0.11.Final]
    at org.hibernate.boot.registry.internal.StandardServiceRegistryImpl.initiateService(StandardServiceRegistryImpl.java:88) [hibernate-core-5.0.11.Final.jar:5.0.11.Final]
    at org.hibernate.service.internal.AbstractServiceRegistryImpl.createService(AbstractServiceRegistryImpl.java:254) [hibernate-core-5.0.11.Final.jar:5.0.11.Final]
    at org.hibernate.service.internal.AbstractServiceRegistryImpl.initializeService(AbstractServiceRegistryImpl.java:228) [hibernate-core-5.0.11.Final.jar:5.0.11.Final]
    at org.hibernate.service.internal.AbstractServiceRegistryImpl.getService(AbstractServiceRegistryImpl.java:207) [hibernate-core-5.0.11.Final.jar:5.0.11.Final]
    at org.hibernate.engine.jdbc.internal.JdbcServicesImpl.configure(JdbcServicesImpl.java:51) [hibernate-core-5.0.11.Final.jar:5.0.11.Final]
    at org.hibernate.boot.registry.internal.StandardServiceRegistryImpl.configureService(StandardServiceRegistryImpl.java:94) [hibernate-core-5.0.11.Final.jar:5.0.11.Final]
    at org.hibernate.service.internal.AbstractServiceRegistryImpl.initializeService(AbstractServiceRegistryImpl.java:237) [hibernate-core-5.0.11.Final.jar:5.0.11.Final]
    at org.hibernate.service.internal.AbstractServiceRegistryImpl.getService(AbstractServiceRegistryImpl.java:207) [hibernate-core-5.0.11.Final.jar:5.0.11.Final]
    at org.hibernate.boot.model.process.spi.MetadataBuildingProcess.handleTypes(MetadataBuildingProcess.java:352) [hibernate-core-5.0.11.Final.jar:5.0.11.Final]
    at org.hibernate.boot.model.process.spi.MetadataBuildingProcess.complete(MetadataBuildingProcess.java:111) [hibernate-core-5.0.11.Final.jar:5.0.11.Final]
    at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.metadata(EntityManagerFactoryBuilderImpl.java:847) [hibernate-entitymanager-5.0.11.Final.jar:5.0.11.Final]
    at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:874) [hibernate-entitymanager-5.0.11.Final.jar:5.0.11.Final]
    at org.springframework.orm.jpa.vendor.SpringHibernateJpaPersistenceProvider.createContainerEntityManagerFactory(SpringHibernateJpaPersistenceProvider.java:60) [spring-orm-4.3.4.RELEASE.jar:4.3.4.RELEASE]
    at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.createNativeEntityManagerFactory(LocalContainerEntityManagerFactoryBean.java:353) [spring-orm-4.3.4.RELEASE.jar:4.3.4.RELEASE]
    at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.buildNativeEntityManagerFactory(AbstractEntityManagerFactoryBean.java:373) [spring-orm-4.3.4.RELEASE.jar:4.3.4.RELEASE]
    at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.afterPropertiesSet(AbstractEntityManagerFactoryBean.java:362) [spring-orm-4.3.4.RELEASE.jar:4.3.4.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1642) [spring-beans-4.3.4.RELEASE.jar:4.3.4.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1579) [spring-beans-4.3.4.RELEASE.jar:4.3.4.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:553) [spring-beans-4.3.4.RELEASE.jar:4.3.4.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:482) [spring-beans-4.3.4.RELEASE.jar:4.3.4.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306) [spring-beans-4.3.4.RELEASE.jar:4.3.4.RELEASE]
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230) [spring-beans-4.3.4.RELEASE.jar:4.3.4.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302) [spring-beans-4.3.4.RELEASE.jar:4.3.4.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197) [spring-beans-4.3.4.RELEASE.jar:4.3.4.RELEASE]
    at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1081) [spring-context-4.3.4.RELEASE.jar:4.3.4.RELEASE]
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:856) [spring-context-4.3.4.RELEASE.jar:4.3.4.RELEASE]
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:542) [spring-context-4.3.4.RELEASE.jar:4.3.4.RELEASE]
    at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.refresh(EmbeddedWebApplicationContext.java:122) [classes/:na]
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:736) [classes/:na]
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:369) [classes/:na]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:313) [classes/:na]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1161) [classes/:na]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1150) [classes/:na]
    at sample.data.jpa.SampleDataJpaApplication.main(SampleDataJpaApplication.java:26) [classes/:na]
Caused by: java.net.ConnectException: Connection refused
    at java.net.PlainSocketImpl.socketConnect(Native Method) ~[na:1.8.0_102]
    at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:350) ~[na:1.8.0_102]
    at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:206) ~[na:1.8.0_102]
    at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:188) ~[na:1.8.0_102]
    at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392) ~[na:1.8.0_102]
    at java.net.Socket.connect(Socket.java:589) ~[na:1.8.0_102]
    at org.postgresql.core.PGStream.<init>(PGStream.java:61) ~[postgresql-9.4.1212.jre7.jar:9.4.1212.jre7]
    at org.postgresql.core.v3.ConnectionFactoryImpl.openConnectionImpl(ConnectionFactoryImpl.java:144) ~[postgresql-9.4.1212.jre7.jar:9.4.1212.jre7]
    ... 51 common frames omitted
xenoterracide commented 7 years ago

I'm of the opinion that the server needs to come up enough that Error pages can be served and that Actuator /health will report a not ok. I don't know if retry can do that. Otherwise you absolutely must put proxies in front of boot in production to ensure users get something. Essentially I would think it should be have as if the database had disappeared in the middle of runtime rather than at the beginning.

michl-b commented 7 years ago

+1

helmbold commented 7 years ago

I stumbled about this behaviour when I tried to start a Spring Boot application along with a database via Docker Compose. The Docker documentations summarizes:

The problem of waiting for a database (for example) to be ready is really just a subset of a much larger problem of distributed systems. In production, your database could become unavailable or move hosts at any time. Your application needs to be resilient to these types of failures. To handle this, your application should attempt to re-establish a connection to the database after a failure. If the application retries the connection, it should eventually be able to connect to the database.

So, I think Spring Boot should provide a retry mechanism (by default). A warning could be logged anyway.

filip-java commented 7 years ago

+1

filip-java commented 7 years ago

Anyone has a workaround?

dsyer commented 7 years ago

This works for me:

package com.example.demo;

import java.sql.Connection;
import java.sql.SQLException;

import javax.sql.DataSource;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.jdbc.datasource.AbstractDataSource;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.EnableRetry;
import org.springframework.retry.annotation.Retryable;

@SpringBootApplication
@EnableRetry
public class DemoApplication {

    @Order(Ordered.HIGHEST_PRECEDENCE)
    private class RetryableDataSourceBeanPostProcessor implements BeanPostProcessor {
        @Override
        public Object postProcessBeforeInitialization(Object bean, String beanName)
                throws BeansException {
            if (bean instanceof DataSource) {
                bean = new RetryableDataSource((DataSource)bean);
            }
            return bean;
        }

        @Override
        public Object postProcessAfterInitialization(Object bean, String beanName)
                throws BeansException {
            return bean;
        }
    }

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

    @Bean
    public BeanPostProcessor dataSouceWrapper() {
        return new RetryableDataSourceBeanPostProcessor();
    }
}

class RetryableDataSource extends AbstractDataSource {

    private DataSource delegate;

    public RetryableDataSource(DataSource delegate) {
        this.delegate = delegate;
    }

    @Override
    @Retryable(maxAttempts=10, backoff=@Backoff(multiplier=2.3, maxDelay=30000))
    public Connection getConnection() throws SQLException {
        return delegate.getConnection();
    }

    @Override
    @Retryable(maxAttempts=10, backoff=@Backoff(multiplier=2.3, maxDelay=30000))
    public Connection getConnection(String username, String password)
            throws SQLException {
        return delegate.getConnection(username, password);
    }

}

I tested it with postgres:

spring.datasource.url=jdbc:postgresql://localhost/postgres
spring.datasource.password=postgres
spring.datasource.username=postgres
logging.level.org.org.springframework.retry=DEBUG
management.security.enabled=false
eichstaedtk commented 7 years ago

I tested it using SpringBoot 1.3.6 with Flyway and MYSQL Database and these solution failed. Any further advices are available?

dsyer commented 7 years ago

How about "don't use Spring Boot 1.3.6"? I used 1.5.3.

iNikem commented 7 years ago

Any plans for this issue?

philwebb commented 7 years ago

@iNikem Nothing imminent I'm afraid. We're currently working on other items that we need to get in to 2.0 because they will break back compatibility.

serhiikartashov commented 6 years ago

@philwebb can we expect this enhancement in Spring Boot 2.0? I suppose this is very useful feature that will make Spring Boot much more reliable. Connection to database can be one of the pitfalls in software development.

mykola-dev commented 6 years ago

I can't believe this feature hasn't been implemented yet! I have an app with 2 datasources. One datasource is optional and can be disabled at all. So now i forced to wrap this datasource into some custom orm just because spring crashes when spring data and hibernate haven't catch the db.

wilkinsona commented 6 years ago

@deviant-studio The feature hasn't been implemented yet as no one on the core team has had time to tackle it and no one from the community has contributed an implementation. Perhaps you'd like to open a pull request that contributes something? Failing that, you could try the example provided by @dsyer and provide some feedback on whether or not it worked for you and what, if anything, you'd like to be done differently.

mykola-dev commented 6 years ago

unfortunately solution above doesn't work for me. looks like the main thread is blocked until connection established, so i can't use my rest api when secondary db is down.

wilkinsona commented 6 years ago

looks like the main thread is blocked until connection established

That's exactly what will happen if something attempts to get a connection. If you want your app to be able to start up and function without its secondary database, this issue isn't going to help you. It's about retrying the attempt to get a connection. It's not about making everything in an app cope with a database that is unavailable.

It sounds like your second datasource isn't truly optional. Something must be attempting to retrieve a connection from it for the main thread to be blocked. Whatever that thing is, it will have to be able to cope with a connection being unavailable and back off.

xenoterracide commented 6 years ago

I personally would just like actuator to be able to come all the way up, even if the app itself is blocked, so it can respond to health requests appropriately

wilkinsona commented 6 years ago

@xenoterracide As I explained in my comment immediately above yours (which you 👎 ), that's a separate requirement to the one being tracked by this issue. Perhaps you could express your needs more constructively in a separate issue that expands on what you'd like Boot to be able to do and how you'd like it to do it?

Allowing an application to start with a missing DataSource, for example, is non-trivial. It would require everything that depends on the DataSource (directly or indirectly) to cope with the DataSource not being available as a bean or being available but not functioning.

nucatus commented 6 years ago

I see people put some "passion" for this ticket. Since I'm facing a similar issue, it totally makes sense to have such a feature that would be configurable at the datasource level.

Indeed, this should be judged from two point of views:

  1. It is advisable to have a fail fast behaviour in a production environment where orchestration mechanisms will restart the service if failed;
  2. By contrary, in a test environment, especially containerized services, it makes sense to have a retryable approach where the application doesn't fail until a number of retries are exhausted.

@dsyer's example looks to be pretty pertinent and addresses the issue in a transparent manner (although haven't tried it yet).

@xenoterracide, your use case is particular enough such that is not worth increasing the complexity of the configuration mechanisms to accommodate a fail-over mechanism. I suggest you disable spring boot's datasource autoconfiguration altogether and implement the fail-over logic in a custom method that will create and expose your datasource bean.

xenoterracide commented 6 years ago

I don't understand why it's hard to inject a proxy if the datasource is unconnectable, have healthcheck return a failure when it checks and finds the connection unavailable, and simply let other sources handled the exception themselves. The proxy can keep trying for the duration of the app (possibly with a breaker or increased time for retrying).

This seems like the obvious solution and the behavior I would expect from any framework, and in fact how most other frameworks I've used work.

Rather put, most frameworks/webservers don't require a database server to start. Seems to me that spring boot has said if your app requires a database connection then so does the web server (tomcat). You can start web servers without applications at all, so you should do that. The webserver should not require the application to start. An application should not require a database to start. Whether or not it will properly handle requests for most things... is a different matter. Obviously apps won't generally function correctly, but that's why they have error handlers.

apryamostanov commented 5 years ago

Dear All,

I opt for such feature to be added. Ideally it should be possible to control this feature per-method (e.g. via annotation). However global Spring property can also suffice for my individual use case.

DavideLeoni commented 4 years ago

This works for me:

package com.example.demo;

import java.sql.Connection;
import java.sql.SQLException;

import javax.sql.DataSource;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.jdbc.datasource.AbstractDataSource;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.EnableRetry;
import org.springframework.retry.annotation.Retryable;

@SpringBootApplication
@EnableRetry
public class DemoApplication {

  @Order(Ordered.HIGHEST_PRECEDENCE)
  private class RetryableDataSourceBeanPostProcessor implements BeanPostProcessor {
      @Override
      public Object postProcessBeforeInitialization(Object bean, String beanName)
              throws BeansException {
          if (bean instanceof DataSource) {
              bean = new RetryableDataSource((DataSource)bean);
          }
          return bean;
      }

      @Override
      public Object postProcessAfterInitialization(Object bean, String beanName)
              throws BeansException {
          return bean;
      }
  }

  public static void main(String[] args) {
      SpringApplication.run(DemoApplication.class, args);
  }

  @Bean
  public BeanPostProcessor dataSouceWrapper() {
      return new RetryableDataSourceBeanPostProcessor();
  }
}

class RetryableDataSource extends AbstractDataSource {

  private DataSource delegate;

  public RetryableDataSource(DataSource delegate) {
      this.delegate = delegate;
  }

  @Override
  @Retryable(maxAttempts=10, backoff=@Backoff(multiplier=2.3, maxDelay=30000))
  public Connection getConnection() throws SQLException {
      return delegate.getConnection();
  }

  @Override
  @Retryable(maxAttempts=10, backoff=@Backoff(multiplier=2.3, maxDelay=30000))
  public Connection getConnection(String username, String password)
          throws SQLException {
      return delegate.getConnection(username, password);
  }

}

I tested it with postgres:

spring.datasource.url=jdbc:postgresql://localhost/postgres
spring.datasource.password=postgres
spring.datasource.username=postgres
logging.level.org.org.springframework.retry=DEBUG
management.security.enabled=false

Perfectly works for me as well!

Just wanted to add that @Retry expects aspect dependencies.

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
</dependency>
angeagu commented 4 years ago

In my case, using Spring 2.2.5 and MySQL 8, I was able to solve issue just adding in application.yml: spring.jpa.database-platform: org.hibernate.dialect.MySQL5Dialect

bkahlert commented 4 years ago

Which issue exactly did you solve @angeagu ? I suppose you did not mean to say you got your application started with an inaccessible DB just by explicitly setting spring.jpa.database-platform to org.hibernate.dialect.MySQL5Dialect.

angeagu commented 4 years ago

Hi @bkahlert , Yes, I have a Spring 2.2.5 app that was failing at starting as it was unable to connecto to a MySQL DB not running. I added property database-platform: org.hibernate.dialect.MySQL8Dialect to my application.yml and it started fine then.

PaluruSumanth commented 4 years ago

Hi @angeagu, I added "spring.jpa.database-platform: org.hibernate.dialect.SQLServer2012Dialect" to my application.properties and it worked fine. but I would like to know the implications of the addition of this line to properties file.

DrBlury commented 4 years ago

I'm facing the same issue it seems. The application is trying to connect to a postgres on startup and if this postgres is down for a few seconds - the application will fail to start.

rubensa commented 3 years ago

Check this blog post for a very good solution: Delay startup of your Spring Boot application until your DB is up.

If you can't change your application code, you can set this application properties for Hikari to "wait" for the database to be ready and to tell JPA the dialect (so it does not need to check and fail):

spring:
  application:
    name: <redacted>
  datasource:
    url: <redacted>
    username: <redacted>
    password: <redacted>
    driver-class-name: org.postgresql.Driver
    hikari:
      minimum-idle: 0
      maximum-pool-size: 15
      connection-timeout: 10000 #10s
      idle-timeout: 300000 #5m
      max-lifetime: 600000 #10m
      initialization-fail-timeout: -1
      validation-timeout: 1000 #1s
    continue-on-error: true
  jpa:
    open-in-view: false
    database-platform: org.hibernate.dialect.PostgreSQLDialect

see: https://stackoverflow.com/a/60348220/3535783

Blafasel3 commented 1 year ago

Check this blog post for a very good solution: Delay startup of your Spring Boot application until your DB is up.

If you can't change your application code, you can set this application properties for Hikari to "wait" for the database to be ready and to tell JPA the dialect (so it does not need to check and fail):

spring:
  application:
    name: <redacted>
  datasource:
    url: <redacted>
    username: <redacted>
    password: <redacted>
    driver-class-name: org.postgresql.Driver
    hikari:
      minimum-idle: 0
      maximum-pool-size: 15
      connection-timeout: 10000 #10s
      idle-timeout: 300000 #5m
      max-lifetime: 600000 #10m
      initialization-fail-timeout: -1
      validation-timeout: 1000 #1s
    continue-on-error: true
  jpa:
    open-in-view: false
    database-platform: org.hibernate.dialect.PostgreSQLDialect

see: https://stackoverflow.com/a/60348220/3535783

Not sure why this is downvoted, worked perfectly for me and seems by far the easiest solution. Seems in the meantime database.continue-on-error: was deprecated in favor of spring.sql.init.continue-on-error but both seem to work. You just have to tell Hikari in someway or the other what dialect you are using, otherwise it will try to determine the dialect dynamically on connect, which will obviously fail if there is no database available.