功能不够完善,努力改进
背景
一个通用SQL查询的服务器
通过SQL直接去对应的数据库去查询数据并返回
问题
- 多种数据库,连接方式不同
- SQL对应不同的数据库,每次查询需要更换数据源
解决方式
- 多数据源管理
- 数据库存数据库连接方式
- server方法或AOP中切换数据源,调用Mapper时自动切换
代码
动态多数据源抽象类
/**
* @version 1.0
* @descriptions: 该类是参考了package org.springframework.jdbc.datasource.lookup下的AbstractRoutingDataSource
* 区别是取消了InitializingBean接口,自定义数据加载时间,targetDataSources属性Vules修改为必须DataSource类
* 如果你看得早,目前不支持高并发,周末会更新高并发版本
* @author: ykccchen
* @date: 2020/8/10 8:22 下午
*/
@Slf4j
public abstract class AbstractRoutingDataSource extends AbstractDataSource {
private Map<Object, DataSource> targetDataSources;
private Object defaultTargetDataSource;
private boolean lenientFallback = true;
private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
private Map<Object, DataSource> resolvedDataSources;
private DataSource resolvedDefaultDataSource;
public void setTargetDataSources(Map<Object, DataSource> targetDataSources) {
this.targetDataSources = targetDataSources;
}
public void setDefaultTargetDataSource(Object defaultTargetDataSource) {
this.defaultTargetDataSource = defaultTargetDataSource;
}
public void setLenientFallback(boolean lenientFallback) {
this.lenientFallback = lenientFallback;
}
public void setDataSourceLookup(DataSourceLookup dataSourceLookup) {
this.dataSourceLookup = (dataSourceLookup != null ? dataSourceLookup : new JndiDataSourceLookup());
}
/**
* 更新数据源Map
*/
public void updateTargetDataSources() {
if (this.targetDataSources == null) {
throw new IllegalArgumentException("参数 'targetDataSources' 是必须的");
}
if(this.resolvedDataSources == null){
this.resolvedDataSources = new HashMap<Object, DataSource>();
}
for (Map.Entry<Object, DataSource> entry : this.targetDataSources.entrySet()) {
Object lookupKey = resolveSpecifiedLookupKey(entry.getKey());
if(this.resolvedDataSources.containsKey(lookupKey)){
continue;
}
DataSource dataSource = resolveSpecifiedDataSource(entry.getValue());
this.resolvedDataSources.put(lookupKey, dataSource);
}
if (this.defaultTargetDataSource != null) {
this.resolvedDefaultDataSource = resolveSpecifiedDataSource(this.defaultTargetDataSource);
}
}
protected Object resolveSpecifiedLookupKey(Object lookupKey) {
return lookupKey;
}
/**
* 校验数据源类型
* @param dataSource
* @return
* @throws IllegalArgumentException
*/
protected DataSource resolveSpecifiedDataSource(Object dataSource) throws IllegalArgumentException {
if (dataSource instanceof DataSource) {
return (DataSource) dataSource;
}else {
log.error("类型异常 - 只支持 [javax.sql.DataSource] 类型的值 : " + dataSource);
throw new IllegalArgumentException("类型异常 - 只支持 [javax.sql.DataSource] 类型的值 : " + dataSource);
}
}
@Override
public Connection getConnection() throws SQLException {
return determineTargetDataSource().getConnection();
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
return determineTargetDataSource().getConnection(username, password);
}
@Override
@SuppressWarnings("unchecked")
public <T> T unwrap(Class<T> iface) throws SQLException {
if (iface.isInstance(this)) {
return (T) this;
}
return determineTargetDataSource().unwrap(iface);
}
@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return (iface.isInstance(this) || determineTargetDataSource().isWrapperFor(iface));
}
/**
* 选择数据源
* @return
*/
protected DataSource determineTargetDataSource() {
Assert.notNull(this.resolvedDataSources, "数据源没有被初始化");
Object lookupKey = determineCurrentLookupKey();
DataSource dataSource = this.resolvedDataSources.get(lookupKey);
if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
dataSource = this.resolvedDefaultDataSource;
}
if (dataSource == null) {
log.error(String.format("该数据源为空 [%s] ,请检查",lookupKey));
throw new IllegalStateException(String.format("该数据源为空 [%s] ,请检查",lookupKey));
}
return dataSource;
}
/**
* 必须实现的接口,获取数据源的KEY值
* @return
*/
protected abstract Object determineCurrentLookupKey();
}
动态多数据源管理类
/**
* @version 1.0
* @descriptions: 管理动态多数据源类
* @author: ykccchen
* @date: 2020/8/10 8:22 下午
*/
@Slf4j
@Service
public class DynamicDataSourceManage implements InitializingBean {
//这是是自己的密码解密工具
@Autowired
private AESUtils aesUtils;
// 数据源管理Mapper
@Autowired
private SysDatasourceMapper sysDatasourceMapper;
// 多数据源通过Map管理
private Map<Object,DataSource> dataSourceMap;
// 主数据源,读写分离方式需要多配几个
@Autowired
private DataSource dataSource;
// 动态多数据源实习类
@Autowired
private RoutingDataSource CISRoutingDataSource;
/**
* 配置参数类的加载
*/
@Autowired
private ConfigurationValuesConstant propertis;
public DynamicDataSourceManage(){
dataSourceMap = new ConcurrentHashMap<>();
}
private Lock lock = new ReentrantLock();
/**
* 初始化数据源
* @param sysDatasourceEntity
* @return
*/
private DataSource init(SysDataSourceEntity sysDatasourceEntity){
if(containsDataSource(sysDatasourceEntity.getDatasourceNo())){
return dataSourceMap.get(sysDatasourceEntity.getDatasourceNo());
}
String decrypted = aesUtils.decrypted(sysDatasourceEntity.getDatabasePwd());
// 初始化数据源
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl(sysDatasourceEntity.getJdbcUrl());
dataSource.setUsername(sysDatasourceEntity.getDatabaseUser());
dataSource.setPassword(decrypted);
dataSource.setDriverClassName(sysDatasourceEntity.getDatabaseDriver());
dataSource.setMaxActive(propertis.getMaxActive());
dataSource.setMinIdle(propertis.getMinIdle());
// 更新数据源集合
dataSourceMap.put(sysDatasourceEntity.getDatasourceNo(),dataSource);
CISRoutingDataSource.updateTargetDataSources();
return dataSource;
}
/**
* 输入数据源 编号调用DataSource
* @param dataSourceNo 数据源ID
* @return
*/
public DataSource getDataSource(String dataSourceNo){
if (StringUtils.isBlank(dataSourceNo)){
throw new ServerException(DataSourceStatusExceptionEnum.SOURCE_ERROR.getCode(),"数据源为空,请检查数据源是否存在");
}
DataSource dataSource = dataSourceMap.get(dataSourceNo);
if (dataSource == null){
dataSource = setDataSource(dataSourceNo);
}
return dataSource;
}
/**
* 加入新的数据源到Map集合
* @param dataSourceNo 数据源ID
* @return 返回添加成功与否
*/
public DataSource setDataSource(String dataSourceNo){
if (dataSourceMap.containsKey(dataSourceNo)) {
log.warn("数据源: " + dataSourceNo + "数据源被重复添加入集合" );
return dataSourceMap.get(dataSourceNo);
}else {
SysDataSourceEntity sysDatasourceEntity = sysDatasourceMapper.selectOne(new SysDataSourceEntity().setDatasourceNo(dataSourceNo));
if (sysDatasourceEntity == null){
log.error("数据源: " + dataSourceNo + "数据源为空");
throw new ServerException(DataSourceStatusExceptionEnum.SOURCE_ERROR.getCode(),"数据源为空,请检查数据源是否存在");
}
DataSource dataSource = null;
lock.lock();
try {
dataSource = init(sysDatasourceEntity);
}catch (Exception e){
log.error(e.getMessage());
}finally {
lock.unlock();
}
log.info("数据源: " + dataSourceNo + "数据源已被加入集合" );
return dataSource;
}
}
/**
* 加入新的数据源到Map集合
* @param dataSourceNo 数据源ID
* @return 返回添加成功与否
*/
public boolean containsDataSource(String dataSourceNo){
return dataSourceMap.containsKey(dataSourceNo);
}
/**
* 更新数据源
* @param dataSourceNo
*/
public void updateDataSource(String dataSourceNo){
SysDataSourceEntity sysDatasourceEntity = sysDatasourceMapper.selectOne(new SysDataSourceEntity().setDatasourceNo(dataSourceNo));
DataSource dataSource = null;
lock.lock();
try {
dataSource = init(sysDatasourceEntity);
}catch (Exception e){
log.error(e.getMessage());
}finally {
lock.unlock();
}
if (dataSource == null){
log.info("多数据源: " + dataSourceNo + "数据源已被更新" );
}else {
log.info("多数据源: " + dataSourceNo + "数据源是进行的添加操作" );
}
}
/**
* 从数据源Map中移除数据源
* @param dataSourceNo
* @return
*/
public DataSource remove(String dataSourceNo){
if (DataSourceNameEnum.MASTER.getCode().equals(dataSourceNo)){
throw new ServerException(DataSourceStatusExceptionEnum.SOURCE_ERROR.getCode(),"非法移除主数据源");
}
DataSource remove = dataSourceMap.remove(dataSourceNo);
CISRoutingDataSource.updateTargetDataSources();
if (remove == null){
log.error("多数据源: " + dataSourceNo + "数据源不存在,出现了错误的移除" );
}
return remove;
}
/**
* 移除数据源Map中的全部数据源
*
*/
@Deprecated
public void removeAll(){
dataSourceMap.clear();
CISRoutingDataSource.updateTargetDataSources();
log.info("多数据源: 数据源全部移除" );
}
// 后置初始化加载主数据源和多数据源类在sqlSessionFactory的数据源属性
@Override
public void afterPropertiesSet() {
// 初始化Mybatis的默认数据源
dataSourceMap.put(DataSourceNameEnum.MASTER.getCode(),dataSource);
CISRoutingDataSource.setTargetDataSources(dataSourceMap);
// 设置默认数据源为Mybatis默认数据源
CISRoutingDataSource.setDefaultTargetDataSource(dataSource);
CISRoutingDataSource.updateTargetDataSources();
}
}
切换数据源类
/**
* 动态数据源版本
* @version 1.0
* @descriptions: 多数据源调用类,实现org.springframework.jdbc.datasource.lookup下的AbstractRoutingDataSource类的determineCurrentLookupKey
* 在Mapper方法调用后会调用determineCurrentLookupKey方法去Map中去获取需要的connection,返回的是Map的Key值
* @author: ykccchen
* @date: 2020/8/7 8:44 上午
*/
@Slf4j
public class RoutingDataSource extends AbstractRoutingDataSource {
private static final ThreadLocal<String> contextHolder =new ThreadLocal<>();
@Autowired
private DynamicDataSourceManage dynamicDataSourceManage;
@Override
protected Object determineCurrentLookupKey() {
return contextHolder.get();
}
/**
* 该方法用于普通方法中的数据源切换
*/
public void DbSelect(String dataSourceNo){
if (!dynamicDataSourceManage.containsDataSource(dataSourceNo)) {
dynamicDataSourceManage.setDataSource(dataSourceNo);
}
contextHolder.set(dataSourceNo);
log.info("数据源切换到" + dataSourceNo);
}
/**
* 该方法用于AOP与注解的组合方式,
*/
public static void setDataSource(DataSourceNameEnum dataSourceNo){
contextHolder.set(dataSourceNo.getCode());
}
}
动态多数据配置类
/**
* @version 1.0
* @descriptions:
* @author: ykccchen
* @date: 2020/8/7 9:01 下午
*/
@Configuration
@EnableTransactionManagement
//MapperScan是tk.mybatis下的
@MapperScan(basePackages = {"com.mapper"})
@Slf4j
public class CisMybatisConfiguration {
/**
* 参数被省略了
*/
@Bean("dataSource")
@Primary
public DataSource druidDataSource() {
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setUrl(url);
druidDataSource.setUsername(userName);
druidDataSource.setPassword(password);
druidDataSource.setDriverClassName(driveClassName);
return druidDataSource;
}
@Bean
public RoutingDataSource dynamicDataSource(){
// 初始化多数据源类
RoutingDataSource routingDataSource = new RoutingDataSource();
return routingDataSource;
}
@Bean(name = "sqlSessionFactory")
@Primary
public SqlSessionFactory sqlSessionFactoryBean() throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dynamicDataSource());
return sqlSessionFactory;
}
@Bean("sqlSessionTemplate")
@Primary
public SqlSessionTemplate sqlSessionTemplate(@Qualifier("sqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
@Bean("transactionManager")
@Primary
public DataSourceTransactionManager transactionManager() {
return new DataSourceTransactionManager(dynamicDataSource());
}
}