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

Spring Boot运行时切换数据库及多库同时使用实现方案咨询

Spring Boot动态多数据源实现方案

前置说明

你之前修改spring.profiles.active不生效,是因为Spring Boot默认在启动时加载配置并初始化数据源等核心Bean,运行时修改配置不会触发Bean的重新初始化,无法实现数据源切换。

需求1:运行时动态切换H2/MySQL数据源

采用Spring官方提供的AbstractRoutingDataSource实现动态路由数据源,无需重启服务即可切换数据源,侵入性低符合开发规范:

实现步骤

  • 新增双数据源配置
    在application.properties中同时配置两个数据源的连接参数:
# H2数据源
spring.datasource.h2.jdbc-url=jdbc:h2:mem:petclinic
spring.datasource.h2.driver-class-name=org.h2.Driver
spring.datasource.h2.username=sa
spring.datasource.h2.password=

# MySQL数据源
spring.datasource.mysql.jdbc-url=jdbc:mysql://localhost:3306/petclinic?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8
spring.datasource.mysql.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.mysql.username=root
spring.datasource.mysql.password=your_password
  • 编写数据源上下文持有类
    用ThreadLocal存储当前线程的数据源标识,保证线程安全:
public class DataSourceContextHolder {
    private static final ThreadLocal<String> DATASOURCE_KEY = new ThreadLocal<>();
    public static final String H2 = "H2";
    public static final String MYSQL = "MYSQL";

    // 设置当前使用的数据源
    public static void setDataSourceKey(String key) {
        DATASOURCE_KEY.set(key);
    }

    // 获取当前使用的数据源
    public static String getDataSourceKey() {
        return DATASOURCE_KEY.get();
    }

    // 清除数据源标识,恢复默认
    public static void clear() {
        DATASOURCE_KEY.remove();
    }
}
  • 自定义动态路由数据源
    继承AbstractRoutingDataSource重写路由逻辑:
public class DynamicDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceContextHolder.getDataSourceKey();
    }
}
  • 配置数据源Bean
    将两个数据源注册到动态路由容器中:
@Configuration
public class DynamicDataSourceConfig {
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.h2")
    public DataSource h2DataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.mysql")
    public DataSource mysqlDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Primary
    @Bean
    public DataSource dynamicDataSource(DataSource h2DataSource, DataSource mysqlDataSource) {
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        // 注册所有数据源
        Map<Object, Object> dataSourceMap = new HashMap<>();
        dataSourceMap.put(DataSourceContextHolder.H2, h2DataSource);
        dataSourceMap.put(DataSourceContextHolder.MYSQL, mysqlDataSource);
        dynamicDataSource.setTargetDataSources(dataSourceMap);
        // 设置默认数据源为H2
        dynamicDataSource.setDefaultTargetDataSource(h2DataSource);
        return dynamicDataSource;
    }
}

使用方法

需要切换数据源时直接调用上下文类的方法即可,原有Repository代码无需修改:

// 切换到MySQL
DataSourceContextHolder.setDataSourceKey(DataSourceContextHolder.MYSQL);
// 执行数据库操作
petRepository.save(pet);
// 操作完成后清除标识,恢复默认数据源
DataSourceContextHolder.clear();

需求2:双数据库同步写入

推荐使用AOP切面实现无侵入式双写,符合通用开发约定:

实现步骤

  • 自定义双写注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DualWrite {
}
  • 编写双写切面逻辑
@Aspect
@Component
public class DualWriteAspect {
    @Around("@annotation(com.example.petclinic.annotation.DualWrite)")
    public Object dualWrite(ProceedingJoinPoint joinPoint) throws Throwable {
        // 先执行主数据源操作
        Object result = joinPoint.proceed();
        // 异步写入次数据源,避免影响主流程性能,可根据需求调整为同步
        new Thread(() -> {
            try {
                String currentDs = DataSourceContextHolder.getDataSourceKey();
                // 切换到另一个数据源
                String targetDs = DataSourceContextHolder.H2.equals(currentDs) ? DataSourceContextHolder.MYSQL : DataSourceContextHolder.H2;
                DataSourceContextHolder.setDataSourceKey(targetDs);
                // 执行相同的保存操作
                joinPoint.proceed();
            } catch (Throwable e) {
                // 可添加日志、重试机制处理写入失败的情况
                e.printStackTrace();
            } finally {
                DataSourceContextHolder.clear();
            }
        }).start();
        return result;
    }
}

使用方法

在需要双写的Repository方法上添加@DualWrite注解即可:

public interface PetRepository extends JpaRepository<Pet, Integer> {
    @Override
    @DualWrite
    <S extends Pet> S save(S entity);

    @Override
    @DualWrite
    <S extends Pet> List<S> saveAll(Iterable<S> entities);
}

注意事项

  • 如果需要双写的强一致性,可引入分布式事务组件保证两边数据同时成功/失败
  • 需保证H2和MySQL的表结构、实体类映射完全一致,避免写入报错

内容的提问来源于stack exchange,提问作者Alfred Kaminski

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.24 05:24:00