如何实现Spring Boot Data/Postgres启动时数据库不可用自动重连?
Got it, let's tackle this problem head-on. When your Spring Boot app starts up and Postgres isn't available, it crashes with that annoying java.net.ConnectException: database: Connection refused error. You mentioned using Spring Retry to wrap the EntityManagerFactory bean for automatic retries—here's a step-by-step implementation to make that work:
Step 1: Add Required Dependencies
First, you need to include Spring Retry and Spring AOP (since Retry relies on AOP for method interception) in your build file, alongside your existing Spring Data JPA/Postgres dependencies.
For Maven (pom.xml):
<dependencies> <!-- Spring Retry --> <dependency> <groupId>org.springframework.retry</groupId> <artifactId>spring-retry</artifactId> </dependency> <!-- Spring AOP for retry interception --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <!-- Existing Spring Data JPA & Postgres --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> <scope>runtime</scope> </dependency> </dependencies>
For Gradle (build.gradle):
dependencies { implementation 'org.springframework.retry:spring-retry' implementation 'org.springframework.boot:spring-boot-starter-aop' // Existing dependencies implementation 'org.springframework.boot:spring-boot-starter-data-jpa' runtimeOnly 'org.postgresql:postgresql' }
Step 2: Enable Spring Retry
Add the @EnableRetry annotation to your Spring Boot main application class to activate retry functionality:
import org.springframework.retry.annotation.EnableRetry; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication @EnableRetry public class YourApplication { public static void main(String[] args) { SpringApplication.run(YourApplication.class, args); } }
Step 3: Wrap EntityManagerFactory with Retry Logic
We'll override the default EntityManagerFactory bean and wrap its creation logic with retry rules. This ensures that if the initial database connection fails, the app will keep retrying instead of crashing immediately.
Create a configuration class:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.retry.annotation.Retryable; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter; import javax.sql.DataSource; import java.util.Properties; @Configuration public class RetryEntityManagerConfig { private final DataSource dataSource; private final Properties jpaProperties; // Inject auto-configured DataSource and JPA properties from Spring Boot public RetryEntityManagerConfig(DataSource dataSource, org.springframework.boot.autoconfigure.orm.jpa.JpaProperties jpaProperties) { this.dataSource = dataSource; this.jpaProperties = jpaProperties.getProperties(); } @Bean @Retryable( value = {java.net.ConnectException.class}, maxAttempts = 10, // Total number of retry attempts backoff = @org.springframework.retry.annotation.Backoff(delay = 2000, multiplier = 1.5) // 2s initial delay, 1.5x increase each retry ) public LocalContainerEntityManagerFactoryBean entityManagerFactory() { LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean(); em.setDataSource(dataSource); em.setPackagesToScan("com.yourpackage.entity"); // Replace with your entity package em.setJpaVendorAdapter(new HibernateJpaVendorAdapter()); em.setJpaProperties(jpaProperties); return em; } }
Key Details:
- We reuse Spring Boot's auto-configured
DataSourceandJpaPropertiesso you don't have to redefine all database credentials manually. - The
@Retryableannotation targetsConnectExceptionspecifically—this avoids retrying for unrelated JPA/Hibernate errors. - Adjust
maxAttemptsandbackoffto match your needs:delayis the initial wait time in milliseconds,multipliermakes the delay grow exponentially (e.g., 2s → 3s → 4.5s).
Step 4: Optional: Add a Recovery Fallback
If you want to handle the scenario where retries eventually fail (e.g., Postgres never comes online), add a @Recover method to define fallback behavior:
Add this to the same configuration class:
import org.springframework.retry.annotation.Recover; @Recover public LocalContainerEntityManagerFactoryBean recoverConnectException(java.net.ConnectException e) { // Log the final failure or implement custom logic here throw new RuntimeException("Failed to connect to Postgres after all retries", e); }
Testing the Setup
- Start your Spring Boot app before starting Postgres. You'll see retry attempts in the logs (with delays between them) instead of an immediate crash.
- Start Postgres during the retry window—your app will successfully connect on the next attempt and finish startup normally.
Sample Log Output:
2024-05-20 12:00:00.000 WARN 12345 --- [ main] o.h.e.j.s.SqlExceptionHelper : SQL Error: 0, SQLState: 08001 2024-05-20 12:00:00.001 ERROR 12345 --- [ main] o.h.e.j.s.SqlExceptionHelper : Connection to localhost:5432 refused. Check that the hostname and port are correct and that the postmaster is accepting TCP/IP connections. 2024-05-20 12:00:02.002 INFO 12345 --- [ main] o.s.retry.support.RetryTemplate : Retry: count=1 ... 2024-05-20 12:00:15.000 INFO 12345 --- [ main] o.h.j.i.u.LogHelper : HHH000204: Processing PersistenceUnitInfo [name: default]
内容的提问来源于stack exchange,提问作者Yaroslav Isaiev

