继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

spring 动态切换、添加数据源实现以及源码浅析

青春有我
关注TA
已关注
手记 1243
粉丝 205
获赞 1010

公司项目需求,由于要兼容老系统的数据库结构,需要搭建一个 可以动态切换、添加数据源的后端服务。

参考了过去的项目,通过配置多个SqlSessionFactory 来实现多数据源,这么做的话,未免过于笨重,而且无法实现动态添加数据源这个需求

webp


通过 spring AbstractRoutingDataSource 为我们抽象了一个 DynamicDataSource 解决这一问题

webp


简单分析下 AbstractRoutingDataSource 的源码


webp

webp

targetDataSources 就是我们的多个数据源,在初始化的时候会调用afterPropertiesSet(),去解析我们的数据源 然后 put 到 resolvedDataSources

webp

实现了 DataSource 的 getConnection(); 我们看看 determineTargetDataSource(); 做了什么

webp

通过下面的 determineCurrentLookupKey();(这个方法需要我们实现) 返回一个key,然后从 resolvedDataSources (其实也就是 targetDataSources) 中 get 一个数据源,实现了每次调用 getConnection(); 打开连接 切换数据源,如果想动态添加的话 只需要重新 set targetDataSources 再调用 afterPropertiesSet() 即可

Talk is cheap. Show me the code

我使用的springboot版本为 1.5.x,下面是核心代码
完整代码:https://gitee.com/yintianwen7/spring-dynamic-datasource

/**
 * 多数据源配置
 * 
 * @author Taven
 *
 */@Configuration@MapperScan("com.gitee.taven.mapper")public class DataSourceConfigurer {    /**
     * DataSource 自动配置并注册
     *
     * @return data source
     */
    @Bean("db0")    @Primary
    @ConfigurationProperties(prefix = "datasource.db0")    public DataSource dataSource0() {        return DruidDataSourceBuilder.create().build();
    }    /**
     * DataSource 自动配置并注册
     *
     * @return data source
     */
    @Bean("db1")    @ConfigurationProperties(prefix = "datasource.db1")    public DataSource dataSource1() {        return DruidDataSourceBuilder.create().build();
    }    /**
     * 注册动态数据源
     * 
     * @return
     */
    @Bean("dynamicDataSource")    public DataSource dynamicDataSource() {
        DynamicRoutingDataSource dynamicRoutingDataSource = new DynamicRoutingDataSource();
        Map<Object, Object> dataSourceMap = new HashMap<>();
        dataSourceMap.put("dynamic_db0", dataSource0());
        dataSourceMap.put("dynamic_db1", dataSource1());
        dynamicRoutingDataSource.setDefaultTargetDataSource(dataSource0());// 设置默认数据源
        dynamicRoutingDataSource.setTargetDataSources(dataSourceMap);        return dynamicRoutingDataSource;
    }    /**
     * Sql session factory bean.
     * Here to config datasource for SqlSessionFactory
     * <p>
     * You need to add @{@code @ConfigurationProperties(prefix = "mybatis")}, if you are using *.xml file,
     * the {@code 'mybatis.type-aliases-package'} and {@code 'mybatis.mapper-locations'} should be set in
     * {@code 'application.properties'} file, or there will appear invalid bond statement exception
     *
     * @return the sql session factory bean
     */
    @Bean
    @ConfigurationProperties(prefix = "mybatis")    public SqlSessionFactoryBean sqlSessionFactoryBean() {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();        // 必须将动态数据源添加到 sqlSessionFactoryBean
        sqlSessionFactoryBean.setDataSource(dynamicDataSource());        return sqlSessionFactoryBean;
    }    /**
     * 事务管理器
     *
     * @return the platform transaction manager
     */
    @Bean
    public PlatformTransactionManager transactionManager() {        return new DataSourceTransactionManager(dynamicDataSource());
    }
}

通过 ThreadLocal 获取线程安全的数据源 key

package com.gitee.taven.config;public class DynamicDataSourceContextHolder {    private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>() {        @Override
        protected String initialValue() {            return "dynamic_db0";
        }
    };    /**
     * To switch DataSource
     *
     * @param key the key
     */
    public static void setDataSourceKey(String key) {
        contextHolder.set(key);
    }    /**
     * Get current DataSource
     *
     * @return data source key
     */
    public static String getDataSourceKey() {        return contextHolder.get();
    }    /**
     * To set DataSource as default
     */
    public static void clearDataSourceKey() {
        contextHolder.remove();
    }

}

动态 添加、切换数据源

/**
 * 动态数据源
 * 
 * @author Taven
 *
 */public class DynamicRoutingDataSource extends AbstractRoutingDataSource {    private final Logger logger = LoggerFactory.getLogger(getClass());    private static Map<Object, Object> targetDataSources = new HashMap<>();    
    /**
     * 设置当前数据源
     *
     * @return
     */
    @Override
    protected Object determineCurrentLookupKey() {
        logger.info("Current DataSource is [{}]", DynamicDataSourceContextHolder.getDataSourceKey());        return DynamicDataSourceContextHolder.getDataSourceKey();
    }    
    @Override
    public void setTargetDataSources(Map<Object, Object> targetDataSources) {        super.setTargetDataSources(targetDataSources);
        DynamicRoutingDataSource.targetDataSources = targetDataSources;
    }    
    /**
     * 是否存在当前key的 DataSource
     * 
     * @param key
     * @return 存在返回 true, 不存在返回 false
     */
    public static boolean isExistDataSource(String key) {        return targetDataSources.containsKey(key);
    }    
    /**
     * 动态增加数据源
     * 
     * @param map 数据源属性
     * @return
     */
    public synchronized boolean addDataSource(Map<String, String> map) {        try {
            Connection connection = null;            // 排除连接不上的错误
            try { 
                Class.forName(map.get(DruidDataSourceFactory.PROP_DRIVERCLASSNAME));
                connection = DriverManager.getConnection(
                        map.get(DruidDataSourceFactory.PROP_URL), 
                        map.get(DruidDataSourceFactory.PROP_USERNAME),
                        map.get(DruidDataSourceFactory.PROP_PASSWORD));
                System.out.println(connection.isClosed());
            } catch (Exception e) {                return false;
            } finally {                if (connection != null && !connection.isClosed()) 
                    connection.close();
            }
            String database = map.get("database");//获取要添加的数据库名
            if (StringUtils.isBlank(database)) return false;            if (DynamicRoutingDataSource.isExistDataSource(database)) return true; 
            DruidDataSource druidDataSource = (DruidDataSource) DruidDataSourceFactory.createDataSource(map);
            druidDataSource.init();
            Map<Object, Object> targetMap = DynamicRoutingDataSource.targetDataSources;
            targetMap.put(database, druidDataSource);            // 当前 targetDataSources 与 父类 targetDataSources 为同一对象 所以不需要set//          this.setTargetDataSources(targetMap);
            this.afterPropertiesSet();
            logger.info("dataSource {} has been added", database);
        } catch (Exception e) {
            logger.error(e.getMessage());            return false;
        }        return true;
    }
    
}

可以通过 AOP 或者 手动 DynamicDataSourceContextHolder.setDataSourceKey(String key) 切换数据源



作者:殷天文
链接:https://www.jianshu.com/p/0a485c965b8b


打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP