You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 20:09:11