Spring Boot 多数据源切换:AbstractRoutingDataSource
在实际项目中,我们常常会用到多个数据库,比如一个主数据库(master)专门用来写入,一个从数据库(slave)专门用来读取。这种场景非常常见,那么问题来了:
我该如何在项目中动态地切换主从数据库?
Spring 是怎么实现“在运行时自动选择数据库”的?
什么是动态数据源?
动态数据源就是在运行时,Spring 动态决定使用哪个数据库连接。
也就是说:
有多个数据库(比如 master、slave)
程序运行过程中自动切换用哪个数据库
你不用手动写 if...else 来判断
第一步:我们需要哪些数据源?
我们以最常见的主从结构举例:
spring:
datasource:
druid:
master:
url: jdbc:mysql://localhost:3306/db_master
username: root
password: 123456
slave:
enabled: true
url: jdbc:mysql://localhost:3306/db_slave
username: root
password: 123456
我们准备两个数据源:
主数据源 master
从数据源 slave(注意加了一个 enabled=true)
第二步:配置两个数据源 Bean
在 Spring Boot 中,我们通过 @Bean 创建连接池:
@Bean
@ConfigurationProperties("spring.datasource.druid.master")
public DataSource masterDataSource(DruidProperties druidProperties) {DruidDataSource dataSource = DruidDataSourceBuilder.create().build();return druidProperties.dataSource(dataSource);
}@Bean
@ConfigurationProperties("spring.datasource.druid.slave")
@ConditionalOnProperty(prefix = "spring.datasource.druid.slave", name = "enabled", havingValue = "true")
public DataSource slaveDataSource(DruidProperties druidProperties) {DruidDataSource dataSource = DruidDataSourceBuilder.create().build();return druidProperties.dataSource(dataSource);
}
这样我们就有了两个数据库连接池。
第三步:创建一个“数据源路由器”
我们不能在代码中每次都自己写 if 判断用哪个数据源,这太麻烦了。
Spring 给我们提供了抽象类AbstractRoutingDataSource,它是一个“动态路由器”,且实现了
DataSource接口
public abstract class AbstractRoutingDataSource
它是怎么工作的?
假设你调用了:
dataSource.getConnection();
如果这个 dataSource 是继承了 AbstractRoutingDataSource 的类,它就会:
- 自动调用 determineCurrentLookupKey()
- 返回一个 key(比如 "MASTER" 或 "SLAVE")
- 根据 key 从 map 中找到对应的数据源
- 最终返回真正的数据库连接
源码也是非常通俗易懂:
可以看到在getConnection方法中,它会先去获取一下DataSource。其中就会先去获取key,如果为空就是默认的DataSource。
第四步:定义我们的 DynamicDataSource 类
public class DynamicDataSource extends AbstractRoutingDataSource {@Overrideprotected Object determineCurrentLookupKey() {return DataSourceContextHolder.getDataSourceType(); // 返回 master 或 slave}
}
它的作用就是:
每次执行数据库操作前,去上下文 ThreadLocal 中取出当前线程应该使用哪个数据源。
第五步:设置 dynamicDataSource Bean
@Bean(name = "dynamicDataSource")
@Primary
public DynamicDataSource dataSource(DataSource masterDataSource) {Map<Object, Object> targetDataSources = new HashMap<>();targetDataSources.put("MASTER", masterDataSource);setDataSource(targetDataSources, "SLAVE", "slaveDataSource");return new DynamicDataSource(masterDataSource, targetDataSources);
}
注意:
@Primary 让 Spring 默认使用这个数据源
setDataSource() 会从 Spring 容器中动态拿到 slaveDataSource
第六步:用 ThreadLocal 保存当前线程的数据源类型
public class DataSourceContextHolder {private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();public static void set(String key) {contextHolder.set(key);}public static String get() {return contextHolder.get();}public static void clear() {contextHolder.remove();}
}
这样每个线程就可以独立地记录当前要使用哪个数据源了!
第七步:通过 AOP 自动切换数据源
你可以加一个注解,比如:
@DS("SLAVE")
public List<User> getUserList() {...
}
AOP 在方法执行前会调用:
DataSourceContextHolder.set("SLAVE");
这样你的方法执行时,就自动使用了从数据库!
整体流程图
最后
多数据源非常适合读写分离、分库分表、租户隔离场景,还是很值得研究一下的。