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
相关产品推荐
相关产品推荐

