由于项目关联多类型的数据源,之前那个开发把每一种数据源都做成了一个Configuration,使用SqlSessionFactory的方式注入。调用的时候用sqlSessionFactory创建connection直接注入SQL,这种写法侵入性很大,所以今天将其重构为基于mybatis的动态数据源注入。
@Autowired @Qualifier("mysqlSqlSessionFactory") private SqlSessionFactory mysqlFactory;
创建数据源对象
public class DynamicDataSource extends AbstractRoutingDataSource {
private static Map<Object, Object> dataSourceMap = new HashMap<>();
private static DynamicDataSource instance;
private static byte[] lock=new byte[0];
public Map<Object, Object> getDataSourceMap() {
return dataSourceMap;
}
/**
* 如果不希望数据源在启动配置时就加载好,可以定制这个方法,从任何你希望的地方读取并返回数据源
* 比如从数据库、文件、外部接口等读取数据源信息,并最终返回一个DataSource实现类对象即可
*/
@Override
protected DataSource determineTargetDataSource() {
return super.determineTargetDataSource();
}
/**
* 如果希望所有数据源在启动配置时就加载好,这里通过设置数据源Key值来切换数据,定制这个方法
*/
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceContextHolder.getDataSourceKey();
}
/**
* 设置默认数据源
* @param defaultDataSource
*/
public void setDefaultDataSource(Object defaultDataSource) {
super.setDefaultTargetDataSource(defaultDataSource);
}
/**
* 设置数据源
* @param dataSources
*/
public void setDataSources(Map<Object, Object> dataSources) {
super.setTargetDataSources(dataSources);
// 将数据源的 key 放到数据源上下文的 key 集合中,用于切换时判断数据源是否有效
DynamicDataSourceContextHolder.addDataSourceKeys(dataSources.keySet());
}
@Override
public void setTargetDataSources(Map<Object, Object> targetDataSources) {
super.setTargetDataSources(targetDataSources);
dataSourceMap.putAll(targetDataSources);
super.afterPropertiesSet();// 必须添加该句,否则新添加数据源无法识别到
}
public static synchronized DynamicDataSource getInstance(){
if(instance==null){
synchronized (lock){
if(instance==null){
instance=new DynamicDataSource();
}
}
}
return instance;
}
}
创建句柄切换数据源
package com.ggg.cheetah.common.config;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public class DynamicDataSourceContextHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>() {
/**
* 将 master 数据源的 key作为默认数据源的 key
*/
@Override
protected String initialValue() {
return "default";
}
};
/**
* 数据源的 key集合,用于切换时判断数据源是否存在
*/
public static List<Object> dataSourceKeys = new ArrayList<>();
/**
* 切换数据源
* @param key
*/
public static void setDataSourceKey(String key) {
contextHolder.set(key);
}
/**
* 获取数据源
* @return
*/
public static String getDataSourceKey() {
return contextHolder.get();
}
/**
* 重置数据源
*/
public static void clearDataSourceKey() {
contextHolder.remove();
}
/**
* 判断是否包含数据源
* @param key 数据源key
* @return
*/
public static boolean containDataSourceKey(String key) {
return dataSourceKeys.contains(key);
}
/**
* 添加数据源keys
* @param keys
* @return
*/
public static boolean addDataSourceKeys(Collection<? extends Object> keys) {
return dataSourceKeys.addAll(keys);
}
}
注入数据源,扫描dao和mapper路径
@Configuration
@MapperScan(basePackages = {"com.ggg.cheetah.datagovernance.dao.mysqldb","com.ggg.cheetah.datagovernance.dao.oracledb","com.ggg.cheetah.datagovernance.dao.sqlserverdb"}, sqlSessionFactoryRef = "dynamicDataSourceFactory")
public class MybatisConfig {
@Value("${spring.datasource.cheetahdb.url}")
private String defaultDBUrl;
@Value("${spring.datasource.cheetahdb.username}")
private String defaultDBUser;
@Value("${spring.datasource.cheetahdb.password}")
private String defaultDBPassword;
@Value("${spring.datasource.cheetahdb.driverClassName}")
private String defaultDBDriverName;
/**
* 初始化之初,把本项目的数据源作为默认数据源注入
*
* @return
*/
@Bean(name = "dynamicDataSource")
public DataSource dynamicDataSource() {
DynamicDataSource dynamicDataSource = DynamicDataSource.getInstance();
DruidDataSource defaultDataSource = new DruidDataSource();
defaultDataSource.setName("default");
defaultDataSource.setUrl(defaultDBUrl);
defaultDataSource.setUsername(defaultDBUser);
defaultDataSource.setPassword(defaultDBPassword);
defaultDataSource.setDriverClassName(defaultDBDriverName);
Map<Object, Object> map = new HashMap<>();
map.put("default", defaultDataSource);
dynamicDataSource.setTargetDataSources(map);
dynamicDataSource.setDefaultDataSource(defaultDataSource);
return dynamicDataSource;
}
@Bean(name = "dynamicDataSourceFactory")
public SqlSessionFactory sqlSessionFactory(
@Qualifier("dynamicDataSource") DataSource dynamicDataSource)
throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dynamicDataSource);
bean.setMapperLocations(
// 设置mybatis的xml所在位置
new PathMatchingResourcePatternResolver().getResources("classpath:mybatis/*db/*Mapper.xml"));
return bean.getObject();
}
@Bean(name = "dynamicDataSourceTemplate")
public SqlSessionTemplate sqlSessionTemplate(
@Qualifier("dynamicDataSourceFactory") SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
service里有两个方法 一个返回所有查询出的数据源信息 driver name url username password
另一个方法设置数据源,将所有数据源注入map,用于切换
@Override
public void setDataSourceInfo() {
DynamicDataSourceContextHolder.setDataSourceKey("default");
List<DruidDataSource> list = this.getList();
//使用自己设置的sourceName作为数据源的key,使用这个进行切换
Map<Object, Object> dataSourceMap = list.stream().collect(Collectors.toMap(x -> x.getName(), x -> {
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setDriverClassName(x.getDriverClassName());
druidDataSource.setUrl(x.getUrl());
druidDataSource.setUsername(x.getUsername());
druidDataSource.setPassword(x.getPassword());
druidDataSource.setKeepAlive(true);
return druidDataSource;
}));
DynamicDataSource dynamicDataSource = DynamicDataSource.getInstance();
Map<Object, Object> map = dynamicDataSource.getDataSourceMap();
map.putAll(dataSourceMap);
dynamicDataSource.setTargetDataSources(map);
dynamicDataSource.afterPropertiesSet();
}
这样就可以动态进行切换了 DynamicDataSourceContextHolder.setDataSourceKey("aaa");
最后调用数据源注入的SQL进行查询,由于是多种数据源,需要建立不同数据源对应的mapper,存放适用的SQL语句。
数据库读写分离也可以用这个思路做。