Spring Boot运行时动态切换数据库连接方案咨询
嘿,这个需求在Spring Boot里其实有成熟的落地方案,我一步步给你拆解清楚,保证你能快速上手:
Spring Boot运行时动态切换数据库连接实现方案
1. 先配置多数据源信息
首先在application.yml(或application.properties)里把所有需要切换的数据库配置都写好,比如:
spring: datasource: db1: url: jdbc:mysql://localhost:3306/db1 username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver db2: url: jdbc:mysql://localhost:3306/db2 username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver
2. 实现动态数据源核心逻辑
Spring提供了AbstractRoutingDataSource类,专门用来做动态数据源路由,我们基于它自定义一个动态数据源类:
public class DynamicDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { // 从线程上下文获取当前要使用的数据源标识 return DataSourceContextHolder.getDataSourceKey(); } }
再写一个线程上下文持有者,用来存储当前线程的数据源标识(避免多线程干扰):
public class DataSourceContextHolder { // 线程本地变量,隔离不同线程的数据源标识 private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>(); // 设置当前线程的数据源标识 public static void setDataSourceKey(String dataSourceKey) { CONTEXT_HOLDER.set(dataSourceKey); } // 获取当前线程的数据源标识 public static String getDataSourceKey() { return CONTEXT_HOLDER.get(); } // 请求结束后清除标识,防止内存泄漏 public static void clearDataSourceKey() { CONTEXT_HOLDER.remove(); } }
3. 配置数据源Bean与动态数据源
在你的@Configuration类里,把各个数据源注册为Bean,再把动态数据源设为主数据源:
@Configuration public class DataSourceConfig { // 注册db1数据源 @Bean @ConfigurationProperties(prefix = "spring.datasource.db1") public DataSource db1DataSource() { return DataSourceBuilder.create().build(); } // 注册db2数据源 @Bean @ConfigurationProperties(prefix = "spring.datasource.db2") public DataSource db2DataSource() { return DataSourceBuilder.create().build(); } // 注册动态数据源,标记为@Primary让Spring优先使用 @Bean @Primary public DataSource dynamicDataSource() { DynamicDataSource dynamicDataSource = new DynamicDataSource(); // 设置默认数据源(当没有指定标识时使用) dynamicDataSource.setDefaultTargetDataSource(db1DataSource()); // 映射数据源标识与实际数据源(BD1对应db1,BD2对应db2) Map<Object, Object> dataSourceMap = new HashMap<>(); dataSourceMap.put("BD1", db1DataSource()); dataSourceMap.put("BD2", db2DataSource()); dynamicDataSource.setTargetDataSources(dataSourceMap); return dynamicDataSource; } }
4. 让Repository自动使用动态数据源
因为我们给dynamicDataSource加了@Primary注解,Spring的JPA、MyBatis等持久层框架会自动优先使用这个动态数据源,所以你的Repository完全不需要额外修改,正常定义即可:
// JPA示例 public interface UserRepository extends JpaRepository<User, Long> { } // MyBatis示例 @Mapper public interface UserMapper { List<User> listAll(); }
5. 根据请求参数切换数据源
这一步需要在请求进入业务逻辑前,从参数里拿到标识并设置到线程上下文,推荐两种实现方式:
方式一:使用拦截器(全局请求拦截)
写一个拦截器,从请求参数里提取数据源标识并设置:
public class DataSourceInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 从请求参数里获取数据源标识,比如参数名是"db" String dbKey = request.getParameter("db"); if (StringUtils.hasText(dbKey)) { // 校验标识合法性,避免非法值 if (Arrays.asList("BD1", "BD2").contains(dbKey)) { DataSourceContextHolder.setDataSourceKey(dbKey); } else { throw new IllegalArgumentException("无效的数据库标识:" + dbKey); } } return true; } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { // 请求结束后清除线程上下文的标识 DataSourceContextHolder.clearDataSourceKey(); } }
然后在Web配置类里注册拦截器:
@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new DataSourceInterceptor()) // 指定要拦截的请求路径,这里拦截所有请求 .addPathPatterns("/**"); } }
方式二:使用AOP(更灵活的局部控制)
如果不想全局拦截,也可以用AOP根据方法注解或参数来切换。先定义一个注解:
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface DataSource { String value(); // 数据源标识,比如BD1、BD2 }
然后写一个切面类:
@Aspect @Component public class DataSourceAspect { @Before("@annotation(dataSource)") public void beforeSwitchDataSource(JoinPoint point, DataSource dataSource) { String dbKey = dataSource.value(); if (StringUtils.hasText(dbKey) && Arrays.asList("BD1", "BD2").contains(dbKey)) { DataSourceContextHolder.setDataSourceKey(dbKey); } else { throw new IllegalArgumentException("无效的数据库标识:" + dbKey); } } @After("@annotation(dataSource)") public void afterSwitchDataSource(JoinPoint point, DataSource dataSource) { DataSourceContextHolder.clearDataSourceKey(); } }
最后在业务方法上加上注解即可:
@Service public class UserService { @Autowired private UserRepository userRepository; @DataSource("BD1") public List<User> getDb1Users() { return userRepository.findAll(); } @DataSource("BD2") public List<User> getDb2Users() { return userRepository.findAll(); } }
关键注意事项
- 务必在请求结束后清除线程上下文的数据源标识,否则线程复用会导致数据源混乱(拦截器的
afterCompletion和AOP的@After都做了这件事)。 - 如果使用MyBatis,要确保
SqlSessionFactory注入的是动态数据源,一般只要把动态数据源Bean注入进去即可。 - 可以把允许的数据源标识放到配置文件里,避免硬编码,提升扩展性。
内容的提问来源于stack exchange,提问作者Jeterson Miranda Gomes
相关产品推荐
相关产品推荐

