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

如何配置Spring Boot+JPA+HikariCP实现数据库故障转移

主备数据源自动切换与故障恢复实现方案(Spring Boot + JPA + HikariCP)

一、核心说明

HikariCP本身不支持多数据源的优先级故障切换与自动恢复机制,MySQL JDBC驱动的多地址配置仅能实现负载均衡,无法满足"优先主库、故障切备、恢复切回"的需求。以下是基于自定义数据源路由+状态监控的落地实现方案。

二、步骤实现

1. 修正多数据源配置

在application.yml中配置三个独立的Hikari连接池,每个池单独配置连接参数:

spring:
  datasource:
    primary:
      jdbcUrl: jdbc:mysql://primary_host:3306/mydb?useSSL=false&serverTimezone=UTC
      username: user1
      password: pass1
      driverClassName: com.mysql.cj.jdbc.Driver
      hikari:
        maximum-pool-size: 10
        connection-test-query: SELECT 1
        connection-timeout: 30000
    secondary1:
      jdbcUrl: jdbc:mysql://secondary_host1:3306/mydb?useSSL=false&serverTimezone=UTC
      username: user2
      password: pass2
      driverClassName: com.mysql.cj.jdbc.Driver
      hikari:
        maximum-pool-size: 10
        connection-test-query: SELECT 1
        connection-timeout: 30000
    secondary2:
      jdbcUrl: jdbc:mysql://secondary_host2:3306/mydb?useSSL=false&serverTimezone=UTC
      username: user3
      password: pass3
      driverClassName: com.mysql.cj.jdbc.Driver
      hikari:
        maximum-pool-size: 10
        connection-test-query: SELECT 1
        connection-timeout: 30000

2. 数据源状态管理

定义数据源类型枚举,并维护各数据源的可用状态:

public enum DataSourceType {
    PRIMARY, SECONDARY1, SECONDARY2
}

@Component
public class DataSourceStatusManager {
    // 当前活跃数据源
    private volatile DataSourceType currentActive = DataSourceType.PRIMARY;
    // 数据源可用状态映射
    private final Map<DataSourceType, Boolean> availableStatus = new ConcurrentHashMap<>();

    public DataSourceStatusManager() {
        // 初始化所有数据源为可用状态
        availableStatus.put(DataSourceType.PRIMARY, true);
        availableStatus.put(DataSourceType.SECONDARY1, true);
        availableStatus.put(DataSourceType.SECONDARY2, true);
    }

    public DataSourceType getCurrentActive() {
        return currentActive;
    }

    public void setCurrentActive(DataSourceType currentActive) {
        this.currentActive = currentActive;
    }

    public boolean isAvailable(DataSourceType type) {
        return availableStatus.getOrDefault(type, false);
    }

    public void setAvailable(DataSourceType type, boolean available) {
        availableStatus.put(type, available);
    }
}

3. 自定义路由数据源

继承Spring的AbstractRoutingDataSource,实现动态数据源路由:

@Component
public class RoutingDataSource extends AbstractRoutingDataSource {

    @Autowired
    private DataSourceStatusManager statusManager;

    @Override
    protected Object determineCurrentLookupKey() {
        // 根据当前活跃数据源返回路由key
        return statusManager.getCurrentActive();
    }
}

4. 配置多数据源与JPA关联

创建配置类,初始化三个Hikari连接池并注入到路由数据源,同时配置JPA的实体管理器和事务管理器:

@Configuration
@EnableJpaRepositories(basePackages = "com.yourpackage.repository",
        entityManagerFactoryRef = "routingEntityManagerFactory",
        transactionManagerRef = "routingTransactionManager")
public class DataSourceConfig {

    // 初始化主数据源连接池
    @Bean
    @ConfigurationProperties("spring.datasource.primary.hikari")
    public HikariConfig primaryHikariConfig() {
        return new HikariConfig();
    }

    @Bean
    public DataSource primaryDataSource() {
        return new HikariDataSource(primaryHikariConfig());
    }

    // 初始化备数据源1连接池
    @Bean
    @ConfigurationProperties("spring.datasource.secondary1.hikari")
    public HikariConfig secondary1HikariConfig() {
        return new HikariConfig();
    }

    @Bean
    public DataSource secondary1DataSource() {
        return new HikariDataSource(secondary1HikariConfig());
    }

    // 初始化备数据源2连接池
    @Bean
    @ConfigurationProperties("spring.datasource.secondary2.hikari")
    public HikariConfig secondary2HikariConfig() {
        return new HikariConfig();
    }

    @Bean
    public DataSource secondary2DataSource() {
        return new HikariDataSource(secondary2HikariConfig());
    }

    // 配置路由数据源为默认数据源
    @Bean
    @Primary
    public DataSource routingDataSource(DataSource primaryDataSource,
                                        DataSource secondary1DataSource,
                                        DataSource secondary2DataSource) {
        RoutingDataSource routingDataSource = new RoutingDataSource();
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put(DataSourceType.PRIMARY, primaryDataSource);
        targetDataSources.put(DataSourceType.SECONDARY1, secondary1DataSource);
        targetDataSources.put(DataSourceType.SECONDARY2, secondary2DataSource);
        routingDataSource.setTargetDataSources(targetDataSources);
        routingDataSource.setDefaultTargetDataSource(primaryDataSource);
        return routingDataSource;
    }

    // 配置JPA实体管理器
    @Bean
    public LocalContainerEntityManagerFactoryBean routingEntityManagerFactory(
            EntityManagerFactoryBuilder builder, @Qualifier("routingDataSource") DataSource dataSource) {
        return builder
                .dataSource(dataSource)
                .packages("com.yourpackage.entity")
                .persistenceUnit("routingPU")
                .properties(jpaProperties())
                .build();
    }

    // 配置事务管理器
    @Bean
    public PlatformTransactionManager routingTransactionManager(
            @Qualifier("routingEntityManagerFactory") EntityManagerFactory entityManagerFactory) {
        return new JpaTransactionManager(entityManagerFactory);
    }

    // JPA基础配置
    private Map<String, Object> jpaProperties() {
        Map<String, Object> props = new HashMap<>();
        props.put("hibernate.hbm2ddl.auto", "none");
        props.put("hibernate.show_sql", false);
        props.put("hibernate.dialect", "org.hibernate.dialect.MySQL8Dialect");
        return props;
    }
}

5. 数据源监控与自动切换

创建定时任务,定期检测数据源可用性,实现故障切换与自动恢复:

@Component
@EnableScheduling
public class DataSourceMonitor {

    @Autowired
    private DataSourceStatusManager statusManager;
    @Autowired
    private DataSource primaryDataSource;
    @Autowired
    private DataSource secondary1DataSource;
    @Autowired
    private DataSource secondary2DataSource;

    // 每10秒检测一次数据源状态
    @Scheduled(fixedRate = 10000)
    public void monitorDataSources() {
        // 检测主数据源可用性
        boolean primaryAvailable = isDataSourceAvailable(primaryDataSource);
        statusManager.setAvailable(DataSourceType.PRIMARY, primaryAvailable);

        // 检测备数据源可用性
        boolean secondary1Available = isDataSourceAvailable(secondary1DataSource);
        statusManager.setAvailable(DataSourceType.SECONDARY1, secondary1Available);
        boolean secondary2Available = isDataSourceAvailable(secondary2DataSource);
        statusManager.setAvailable(DataSourceType.SECONDARY2, secondary2Available);

        // 切换逻辑:主库可用则切回主库;主库不可用则选第一个可用的备库
        if (primaryAvailable) {
            statusManager.setCurrentActive(DataSourceType.PRIMARY);
        } else {
            if (secondary1Available) {
                statusManager.setCurrentActive(DataSourceType.SECONDARY1);
            } else if (secondary2Available) {
                statusManager.setCurrentActive(DataSourceType.SECONDARY2);
            }
            // 所有数据源不可用时,可添加告警或降级逻辑
        }
    }

    // 检测数据源是否可用
    private boolean isDataSourceAvailable(DataSource dataSource) {
        if (dataSource instanceof HikariDataSource hikariDataSource) {
            try (Connection conn = hikariDataSource.getConnection()) {
                return conn.isValid(3);
            } catch (SQLException e) {
                return false;
            }
        }
        return false;
    }
}

6. 异常触发即时切换(可选)

在全局异常处理器中捕获数据库连接异常,触发即时切换:

@RestControllerAdvice
public class GlobalExceptionHandler {

    @Autowired
    private DataSourceStatusManager statusManager;
    @Autowired
    private DataSourceMonitor dataSourceMonitor;

    @ExceptionHandler({SQLException.class, CannotGetJdbcConnectionException.class})
    public ResponseEntity<String> handleDbConnectionException(Exception e) {
        // 标记主数据源不可用
        statusManager.setAvailable(DataSourceType.PRIMARY, false);
        // 手动触发一次检测切换
        dataSourceMonitor.monitorDataSources();
        return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).body("数据源切换中,请稍后重试");
    }
}

三、注意事项

  • 确保外部组件已保障三个数据源的数据一致性,本方案仅负责路由切换
  • HikariCP的connection-test-query配置可提前检测连接有效性,减少运行时异常
  • 定时检测频率可根据业务需求调整,避免过于频繁影响系统性能
  • 需添加所有数据源不可用时的告警或降级逻辑

内容的提问来源于stack exchange,提问作者TF-Boy

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 04:03:16