码云:switch-datasource-spring-boot-starter: 自定义starter,使用注解实现动态切换数据源
一,原理
我们知道mybatis的一般执行流程主要为:
加载配置文件 ----> 创建SqlSessionFactory -----> 获取sqlsession ----->调用mapper方法执行sql。
其中读取数据源创建DataSource是在SqlSessionFactory之前创建的,后面一系列流程是固定的,所有我们想动态切换数据源,只需将数据源切换为我们想要的就能实现动态切换。
这是就需要一个类保存数据源信息供sqlSessionFactory供其读取,而spring集成Mybatis的时候,就专门提供了一个这样的类,AbstractRoutingDataSource。
我们下看下AbstractRoutingDataSource这个类的几个重要属性:
targetDataSources:保存了所有数据源的信息,key 是数据源的标识符,value 是对应的 DataSource 对象。 defaultTargetDataSource:默认的当前数据源,如果无法根据当前的 key 值找到对应的数据源,则使用该默认数据源。
resolvedDataSources:解析好的数据源信息,可以根据key数据源标识取得解析好的DataSource。
因为AbstractRoutingDataSource为抽象类,继承的话需要实现一个接口determineCurrentLookupKey。
根据AbstractRoutingDataSource的源码,可以发现当前使用的数据源是根据determineCurrentLookupKey这个方法的返回值去从resolvedDataSources解析好的数据源map集合中取的,所有我们就可以继承AbstractRoutingDataSource这个类,实现determineCurrentLookupKey方法,将返回值设置为我们想要切换的数据源名称,那么mybatis就是根据这个数据源构建sqlSessionFactory,那么后续执行都是基于这个数据源的,从而就是先了动态切换。
还有一点,每个线程使用的数据源可能不同,所以我们可以使用ThreadLocal存放数据源名称,从而保证线程安全。
二,设计分析
1,定义properties读取配置文件
@ConfigurationProperties(prefix = "spring.datasource")
public class DataSourceProperties {
/**
* 数据源信息
*/
private Map<String, Map<String,String>> db;
public Map<String, Map<String, String>> getDb() {
return db;
}
public void setDb(Map<String, Map<String, String>> db) {
this.db = db;
}
}
注:配置类这样定义,配置文件也要按照这个写,如
2,创建DataSourceContextHolder保存当前线程数据源名称
public class DataSourceContextHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
public static void setDataSourceKey(String dataSourceKey){
contextHolder.set(dataSourceKey);
}
public static String getDataSourceKey(){
return contextHolder.get();
}
public static void clearDataSourceKey(){
contextHolder.remove();
}
}
3,创建DynamicDataSource继承AbstractRoutingDataSource
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDataSourceKey();
}
}
4,定义配置类,将DynamicDataSource注册到spring
@Configuration
@EnableConfigurationProperties(DataSourceProperties.class)
public class DataSourceConfig {
@Resource
private DataSourceProperties dataSourceProperties;
@Bean
public DataSource dataSource(){
// 创建数据源
Map<Object, Object> targetDataSources = new HashMap<>();
Map<String, Map<String, String>> db = dataSourceProperties.getDb();
for (String dbInfo : db.keySet()) {
Map<String, String> objMap = db.get(dbInfo);
targetDataSources.put(dbInfo,new DriverManagerDataSource(objMap.get("url"),objMap.get("username"),objMap.get("password")));
}
// 设置数据源
DynamicDataSource dynamicDataSource = new DynamicDataSource();
dynamicDataSource.setTargetDataSources(targetDataSources);
dynamicDataSource.setDefaultTargetDataSource(targetDataSources.get("master"));
return dynamicDataSource;
}
}
5,测试
基于以上配置,就能实现动态数据源切换。
使用
DataSourceContextHolder.setDataSourceKey("数据源名称");
就能实现动态切库,使用完成后可以使用
DataSourceContextHolder.clearDataSourceKey();
就会自动换成默认的主库,master。
三,具体代码
本测试代码,是使用注解实现动态切换,比上面的多了自定义注解,starter和aop的使用。
gitee:switch-datasource-spring-boot-starter: 自定义starter,使用注解实现动态切换数据源