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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.19 04:15:05