Spring Boot JDBC创建可重试数据源报HikariDataSource类型错误
问题描述
需要为应用添加数据库连接重试能力,在应用成功获取数据库连接前按指定次数重试连接。基于spring-retry对DataSource做重试增强后功能未生效,抛出如下异常:
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'jdbcTemplate' defined in class path resource [org/springframework/boot/autoconfigure/jdbc/JdbcTemplateConfiguration.class]: Unsatisfied dependency expressed through method 'jdbcTemplate' parameter 0; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'dataSource' defined in class path resource [org/springframework/boot/autoconfigure/jdbc/DataSourceConfiguration$Hikari.class]: Initialization of bean failed; nested exception is java.lang.IllegalArgumentException: ExistingValue must be an instance of com.zaxxer.hikari.HikariDataSource
备注:build.gradle中声明的依赖均为应用必需依赖,当前代码仅提取了重试相关逻辑片段。
根因分析
报错核心原因是AOP代理类型不匹配:
- Spring Boot自动配置Hikari连接池时,会在容器中注册类型为
HikariDataSource的Bean - 直接对该Bean添加spring-retry的AOP增强时,Spring默认对实现接口的Bean采用JDK动态代理生成代理对象,该代理对象仅实现
DataSource接口,不属于HikariDataSource类型 - Hikari自动配置逻辑回写Bean实例时做类型校验,发现代理对象不是
HikariDataSource实例,直接抛出参数非法异常
解决方案
推荐采用包装类模式实现,完全规避类型不匹配问题,不需要修改全局AOP配置:
- 自定义DataSource包装类,内部持有真实的HikariDataSource实例
- 仅在
getConnection方法上添加重试逻辑,其余接口方法全部委托给真实连接池实例执行 - 将包装类注册为容器中主DataSource Bean,替换原数据源供JdbcTemplate、JPA等上层组件使用
参考实现代码:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.retry.annotation.Backoff; import org.springframework.retry.annotation.Retryable; import com.zaxxer.hikari.HikariDataSource; import javax.sql.DataSource; import java.io.PrintWriter; import java.sql.Connection; import java.sql.SQLException; import java.sql.SQLFeatureNotSupportedException; import java.util.logging.Logger; @Configuration public class DsRetryConfig { @Bean @Primary public DataSource dataSource(HikariDataSource originalHikariDs) { return new RetryableDataSource(originalHikariDs); } public static class RetryableDataSource implements DataSource { private final DataSource delegate; public RetryableDataSource(DataSource delegate) { this.delegate = delegate; } @Override @Retryable( maxAttempts = 5, // 按实际需求配置重试次数 value = SQLException.class, // 仅对SQL连接异常重试 backoff = @Backoff(delay = 1000, multiplier = 2) // 重试间隔策略 ) public Connection getConnection() throws SQLException { return delegate.getConnection(); } @Override @Retryable( maxAttempts = 5, value = SQLException.class, backoff = @Backoff(delay = 1000, multiplier = 2) ) public Connection getConnection(String username, String password) throws SQLException { return delegate.getConnection(username, password); } // 以下方法全部直接委托给原始连接池,不添加重试逻辑 @Override public PrintWriter getLogWriter() throws SQLException { return delegate.getLogWriter(); } @Override public void setLogWriter(PrintWriter out) throws SQLException { delegate.setLogWriter(out); } @Override public void setLoginTimeout(int seconds) throws SQLException { delegate.setLoginTimeout(seconds); } @Override public int getLoginTimeout() throws SQLException { return delegate.getLoginTimeout(); } @Override public Logger getParentLogger() throws SQLFeatureNotSupportedException { return delegate.getParentLogger(); } @Override public <T> T unwrap(Class<T> iface) throws SQLException { return delegate.unwrap(iface); } @Override public boolean isWrapperFor(Class<?> iface) throws SQLException { return delegate.isWrapperFor(iface); } } }
注意:不要在DataSource Bean初始化阶段主动触发连接获取,否则重试逻辑不会生效,重试仅在业务调用getConnection方法时触发。
如果不想写包装类,也可以通过全局配置强制AOP使用CGLIB代理,让代理对象继承HikariDataSource类型,在application.yml中添加配置:
spring: aop: proxy-target-class: true
该方案需要注意HikariDataSource不能存在final类、final方法,否则CGLIB代理会失败,稳定性不如包装类方案。
内容的提问来源于stack exchange,提问作者Rajeev
相关产品推荐
相关产品推荐

