SpringBoot+Mybatis+多类型数据库 实现一个 支持读写分离的动态多数据源

功能不够完善,努力改进

背景

一个通用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());
    }
}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值