1、AbstractRoutingDataSource介绍
动态数据源其实就是针对系统原本的DataSource数据源套上了一个适配器,或者叫路由
即:
AbstractRoutingDataSource
它本身实现DataSource接口,初始化时将自己实例化成数据源dataSource的bean对象
当mybatis或者其他插件获取数据源dataSource的bean对象时,就获取到它了。
(1)它内部实现了一个map用来存放多个数据源,做key和数据源的映射
(2)提供默认数据源设置
(3)提供设置数据源key的回调函数,根据key返回对应数据源(切换数据源,肯定是针对当前工作的线程,而不是全局,因此线程1切换数据源不能影响线程2正常工作,因此该key存放应存放在线程的变量中)。
(4)系统每次调用数据源的时候它都会先获取key来确定数据源。没有key或者key不存在就是默认。
2、使用
我们只在需要切换数据源的地方修改一些线程变量key即可
3、代码实现
(1)创建线程变量key
public class DynamicDataSourceContextHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
public static void setDataSourceKey(String key) {
contextHolder.set(key);
}
public static void useMaster() {
contextHolder.set("master");
}
public static void useSlave() {
contextHolder.set("slave");
}
public static String getDataSourceKey() {
return contextHolder.get();
}
public static void clearDataSourceKey() {
contextHolder.remove();
}
}
(2)创建动态数据源类
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class DynamicDatasource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceContextHolder.getDataSourceKey();
}
}
(3)初始化数据源
这块代码因为发现@ConfigurationProperties注解不生效,所以就手动写的数据源配置
@Configuration
@EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class})
public class DynamicDatasourceConfig {
@Value("${spring.datasource.dynamic.datasource.master.url}")
String masterUrl;
@Value("${spring.datasource.dynamic.datasource.master.driver-class-name}")
String masterDriver;
@Value("${spring.datasource.dynamic.datasource.master.username}")
String masterUsername;
@Value("${spring.datasource.dynamic.datasource.master.password}")
String masterPassword;
@Value("${spring.datasource.dynamic.datasource.slave.url}")
String slaveUrl;
@Value("${spring.datasource.dynamic.datasource.slave.driver-class-name}")
String slaveDriver;
@Value("${spring.datasource.dynamic.datasource.slave.username}")
String slaveUsername;
@Value("${spring.datasource.dynamic.datasource.slave.password}")
String slavePassword;
@Bean("master")
@ConfigurationProperties(prefix = "spring.datasource.dynamic.datasource.master")
public DataSource masterDatasource(){
return DataSourceBuilder.create().url(masterUrl).driverClassName(masterDriver).username(masterUsername).password(masterPassword).build();
}
@Bean("slave")
@ConfigurationProperties(prefix = "spring.datasource.dynamic.datasource.slave")
public DataSource slaveDatasource(){
return DataSourceBuilder.create().url(slaveUrl).driverClassName(slaveDriver).username(slaveUsername).password(slavePassword).build();
}
@Bean
@Primary
public DataSource dataSource(){
Map<Object, Object> dataSourceMap = new HashMap<>(2);
dataSourceMap.put("master", masterDatasource());
dataSourceMap.put("slave", slaveDatasource());
DynamicDatasource dynamicDatasource=new DynamicDatasource();
dynamicDatasource.setTargetDataSources(dataSourceMap);
dynamicDatasource.setDefaultTargetDataSource(masterDatasource());
return dynamicDatasource;
}
}
(4)使用样例
DynamicDataSourceContextHolder.useSlave();
DynamicDataSourceContextHolder.useMaster();
/**
* 从库同步给主库
*/
@PostMapping("/slaveToMaster")
public Object slaveToMaster(@RequestParam(name="tableName")String tableName,
@RequestParam(name="field")String field,
@RequestParam(name="whereSQL")String whereSQL) throws SQLException {
log.info("当前功能:将从库数据同步到主库中");
DynamicDataSourceContextHolder.useSlave();
List<Map<String,Object>> datas=baseSQLMapper.selectById(tableName,field,whereSQL);
log.info("查询到从库数据共{}条",datas==null?0:datas.size());
DynamicDataSourceContextHolder.useMaster();
if(datas!=null&& !datas.isEmpty()){
log.info("先清理主库【{}】表的数据",tableName);
baseSQLMapper.deleteBeforeInsert(tableName,whereSQL);
log.info("开始同步数据:【{}】",tableName);
String[] fields=field.split(",");
ProgressUtils.calculate(datas, 5, new ProgressUtils.ProgressUtilsForEachCallBack<Map<String, Object>>() {
@Override
public void process(Map<String, Object> map, int index) {
List<Object> data=new ArrayList<>();
for (String f:fields) {
data.add(map.get(f.trim()));
}
baseSQLMapper.insert(tableName,field,data);
}
@Override
public void notify(int progress, int index) {
log.info("同步【{}】进度:{}%",tableName,progress);
}
});
log.info("同步完成:【{}】",tableName);
return "传输成功";
}else{
return "未查询到任何数据";
}
}