Spring Boot 2.0动态多数据源切换实战教程

张开发
2026/4/14 23:27:25 15 分钟阅读

分享文章

Spring Boot 2.0动态多数据源切换实战教程
在企业级应用开发中多数据源切换是一个非常常见的需求。无论是为了实现读写分离主库写从库读、分库分表还是多租户架构掌握动态数据源切换都是进阶 Java 开发的必备技能。本教程将带你从零开始基于Spring Boot和AbstractRoutingDataSource实现一个轻量级、高性能的动态数据源切换方案。一、核心原理AbstractRoutingDataSourceSpring 提供了一个名为AbstractRoutingDataSource的抽象类它是实现动态切换的核心。工作机制路由键Lookup Key系统维护一个“地图”Key 是数据源标识如 master, slaveValue 是具体的 DataSource 对象。动态决策每次数据库操作前Spring 会调用determineCurrentLookupKey()方法。线程隔离我们通常使用ThreadLocal来存储当前线程应该使用的数据源标识确保多线程环境下互不干扰。流程业务代码→AOP切面(设置ThreadLocal)→AbstractRoutingDataSource(获取ThreadLocal值)→路由到具体DataSource→执行SQL→AOP切面(清除ThreadLocal)二、环境准备与依赖我们将使用Druid连接池阿里巴巴开源监控功能强大和MyBatis-Plus简化开发。Maven 依赖 (pom.xml)dependencies !-- Web 基础 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- 数据库驱动 -- dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId scoperuntime/scope /dependency !-- Druid 连接池 -- dependency groupIdcom.alibaba/groupId artifactIddruid-spring-boot-starter/artifactId version1.2.16/version /dependency !-- MyBatis-Plus (可选简化Mapper开发) -- dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3.1/version /dependency !-- AOP 支持 (必须) -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-aop/artifactId /dependency !-- Lombok -- dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency /dependencies三、配置文件 (application.yml)我们需要在配置文件中定义多个数据源。spring: datasource: # 主数据源 (Master) master: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/db_master?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: root type: com.alibaba.druid.pool.DruidDataSource # 从数据源 (Slave) slave: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/db_slave?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: root type: com.alibaba.druid.pool.DruidDataSource四、核心代码实现这是最关键的部分我们将分步实现。1、创建数据源枚举用于规范数据源的标识避免魔法值。public enum DataSourceType { MASTER(master), SLAVE(slave); private final String name; DataSourceType(String name) { this.name name; } public String getName() { return name; } }2、实现数据源上下文持有者 (ContextHolder)使用ThreadLocal保证线程安全。public class DataSourceContextHolder { private static final ThreadLocalString CONTEXT_HOLDER new ThreadLocal(); // 设置数据源 key public static void setDataSourceKey(String key) { CONTEXT_HOLDER.set(key); } // 获取数据源 key public static String getDataSourceKey() { return CONTEXT_HOLDER.get(); } // 清除数据源 key (非常重要防止内存泄漏和线程复用导致的数据污染) public static void clearDataSourceKey() { CONTEXT_HOLDER.remove(); } }3、自定义注解为了让代码更优雅我们定义一个注解来标记在 Service 方法上。Target({ElementType.METHOD, ElementType.TYPE}) Retention(RetentionPolicy.RUNTIME) public interface DynamicDataSource { DataSourceType value() default DataSourceType.MASTER; }4、实现动态数据源路由类继承AbstractRoutingDataSource。public class DynamicDataSource extends AbstractRoutingDataSource { Override protected Object determineCurrentLookupKey() { // 从 ThreadLocal 中获取当前应该使用的数据源标识 return DataSourceContextHolder.getDataSourceKey(); } }5、数据源配置类将所有数据源组装到路由类中。Configuration public class DataSourceConfig { Bean ConfigurationProperties(prefix spring.datasource.master) public DataSource masterDataSource() { return DruidDataSourceBuilder.create().build(); } Bean ConfigurationProperties(prefix spring.datasource.slave) public DataSource slaveDataSource() { return DruidDataSourceBuilder.create().build(); } Bean Primary // 默认数据源 public DataSource dynamicDataSource(DataSource masterDataSource, DataSource slaveDataSource) { MapObject, Object targetDataSources new HashMap(); targetDataSources.put(DataSourceType.MASTER.getName(), masterDataSource); targetDataSources.put(DataSourceType.SLAVE.getName(), slaveDataSource); DynamicDataSource routingDataSource new DynamicDataSource(); routingDataSource.setTargetDataSources(targetDataSources); // 默认使用主库Master routingDataSource.setDefaultTargetDataSource(masterDataSource); return routingDataSource; } }6、AOP 切面实现自动切换这是“自动化”的关键。我们需要在方法执行前设置数据源执行后清除。️ 重要提示AOP 切面的优先级 (Order) 必须高于事务管理器 (Transactional)。因为事务开启后连接就已经获取了此时再切换数据源是无效的。Aspect Component Order(1) // 确保 Order 值小于事务的 Order (默认事务是 Ordered.LOWEST_PRECEDENCE) public class DataSourceAspect { Around(annotation(dynamicDataSource)) public Object switchDataSource(ProceedingJoinPoint point, DynamicDataSource dynamicDataSource) throws Throwable { // 1. 获取注解中的值 DataSourceType type dynamicDataSource.value(); // 2. 设置到 ThreadLocal DataSourceContextHolder.setDataSourceKey(type.getName()); try { // 3. 执行目标方法 return point.proceed(); } finally { // 4. 务必在 finally 块中清除防止线程池复用导致数据错乱 DataSourceContextHolder.clearDataSourceKey(); } } }五、业务层使用示例手动切换数据源Service public class UserService { Autowired private UserMapper userMapper; Transactional public void addUser(User user) { userMapper.insert(user); } } // 外层调用方 Service public class OuterService { Autowired private UserService userService; public void businessMethod(User user) { // 先切换数据源 DataSourceContextHolder.master(); try { // 再调用此时开启事务 userService.addUser(user); } finally { DataSourceContextHolder.clearDataSourceKey(); } } }动态切换数据源Service public class UserService { Autowired private UserMapper userMapper; // 默认走主库或者不写注解因为默认是 Master DynamicDataSource(DataSourceType.MASTER) Transactional public void addUser(User user) { userMapper.insert(user); } // 查询操作走从库 DynamicDataSource(DataSourceType.SLAVE) public User getUserById(Long id) { return userMapper.selectById(id); } }六、避坑指南与最佳实践事务与切换顺序这是 90% 的开发者会遇到的坑。如果你使用了Transactional请确保你的数据源切换切面在事务开启之前执行。错误做法先开启事务再切换数据源切换无效仍使用默认库。正确做法如上所示设置Order(1)。ThreadLocal 内存泄漏在使用线程池如 Tomcat 容器的环境中线程会被复用。如果不在finally块中调用remove()清除 ThreadLocal下一个请求可能会错误地继承上一个请求的数据源设置。MyBatis-Plus 配置如果你使用 MyBatis-Plus确保SqlSessionFactory注入的是我们定义的dynamicDataSource而不是具体的masterDataSource。在 Spring Boot 自动配置中通常注入Primary的 Bean 即可。连接池隔离主库和从库应该配置独立的连接池参数。例如主库可能需要更高的max-active连接数而从库可以配置较小的连接数。七、进阶方案使用开源插件如果你觉得手写上述代码太繁琐或者担心维护成本强烈推荐使用开源社区成熟的解决方案dynamic-datasource-spring-boot-starter。优点零代码配置开箱即用。支持 SpEL 表达式如根据 userId 取模分库。支持多数据源嵌套调用。官方维护兼容性好。使用方式引入依赖dynamic-datasource-spring-boot-starter。在application.yml中配置数据源。直接使用DS(slave)注解即可。通过本教程你应该已经掌握了 Spring Boot 下多数据源切换的核心原理和实现细节。无论是手写实现还是使用插件理解底层的AbstractRoutingDataSource和ThreadLocal机制都是至关重要的。

更多文章