zoukankan      html  css  js  c++  java
  • Springboot动态数据源的切换

    一、了解动态数据源场景

    这里介绍一下一个类

    AbstractRoutingDataSource

    这个类是我们切换数据源的核心,我们进入此类了解一番。

    此类中的方法determineTargetDataSource() ,它返回的是一个key,不同的key实现可动态路由的数据源,下面是此方法的源码:

    protected DataSource determineTargetDataSource() {
    		Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
            //这个获得key的方法,可以由自定义的数据源类,继承AbstractRoutingDataSource类,重写
    		Object lookupKey = determineCurrentLookupKey();
            //从数据源的Map中根据单进程获取的key,取出DataSource
    		DataSource dataSource = this.resolvedDataSources.get(lookupKey);
    		if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
                //若map中没有对应的数据源,则取默认的数据源
    			dataSource = this.resolvedDefaultDataSource;
    		}
    		if (dataSource == null) {
    			throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
    		}
    		return dataSource;
    	}
    

    从上面的代码中,我们知道,

    • 这个数据源是封装到Map结构中的
    • 需要设置默认数据源

    这两方面,需要我们在本地,创建一个类,去封装数据源。

    二、封装数据源到Map<Object,DataSource>和设置默认数据源

    在Demo中创建一个类DynamicDatasourceConfiguration;

    对于此类,我们需要从application.yml中获取配置参数,即数据源配置,比如url、username、password、driver-class-name等。

    下面有两种方法获取配置参数:

    • 实现EnvironmentAware接口, 实现setEnvironment(Environment env)此方法,从env参数中可获取数据库配置参数。
    • 可以创建实体类接收,从此类中获取。可以用@ConfigurationProperties(prefix = "")注解。

    我的Demo,从Environment 参数中获取,若数据源多,Springboot启动时,从spring容器中根据beans的name获取到,对应的值,数据源,加载一次即可。

    1.封装数据源类

    如下代码:

    /**
     * @description 数据源配置
     * @author 山沉
     * @公众号 九月的山沉
     * @微信号 Applewith520
     * @个人博客 Choleen95.github.com
     * @博客 https://www.cnblogs.com/Choleen/
     * @since 2021/1/1 22:02
     */
    @Configuration
    @Component
    @MapperScan(basePackages = {"com.example.es.mapper"}, sqlSessionFactoryRef = "sqlSessionFactory")
    @PropertySource(value = "classpath:application.yml")
    public class DynamicDatasourceConfiguration implements EnvironmentAware{
        private static final Logger logger = LoggerFactory.getLogger(DynamicDatasourceRegister.class);
    
    
        private static  String PREFIX = "spring.datasource";
    
        @Override
        public void setEnvironment(Environment env) {
    
            init(env);
        }
        //目标数据源
        private Map<Object, Object> targetDataSources = new HashMap<>();
        //默认数据源
        private Object defaultTargetDataSource;
    
        @Bean("dynamicDataSource")
        public DynamicDatasource init(Environment env){
            String datasourceNames = env.getProperty(PREFIX+".dynamicNames");
            MyAssert.isEmpty(datasourceNames,"dataSource is null");
            String[] array = datasourceNames.split(",");
            DynamicDatasource dynamicDatasource = new DynamicDatasource();
            if(array.length == 1){
                //只有一个
                String type = array[0];
                DataSource dataSource = buildDataSource(env, type);
                targetDataSources.put(type,dataSource);
                defaultTargetDataSource = dataSource;
            }else {
                //多个
                for (String s : array) {
                    DataSource dataSource = buildDataSource(env, s);
                    targetDataSources.put(s,dataSource);
                }
            }
           dynamicDatasource.setTargetDataSources(targetDataSources);
           if(defaultTargetDataSource == null){
               defaultTargetDataSource = targetDataSources.get(array[0]);
               DynamicDataSourceContextHolder.setRouteKey(array[0]);
           }
           dynamicDatasource.setDefaultTargetDataSource(defaultTargetDataSource);
            return dynamicDatasource;
        }
    
    
        public DataSource buildDataSource(Environment props, String type){
            String dbNames = PREFIX+"."+type+ ".";
            String url = props.getProperty(dbNames+"url");
            String username = props.getProperty(dbNames+"username");
            String password = props.getProperty(dbNames+"password");
            String driverClassName = props.getProperty(dbNames+"driver-class-name");
            logger.info("加载数据源--------------->{}",url);
            DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
            dataSource.setUrl(url);
            dataSource.setUsername(username);
            dataSource.setPassword(password);
            dataSource.setDriverClassName(driverClassName);
            return dataSource;
        }
    
        @Bean
        public PlatformTransactionManager txManager(DataSource dynamicDataSource) {
            return new DataSourceTransactionManager(dynamicDataSource);
        }
    
        @Bean("sqlSessionFactory")
        public SqlSessionFactory sqlSessionFactoryBean(@Autowired @Qualifier("dynamicDataSource") DynamicDatasource dynamicDataSource) throws Exception {
            SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
            sqlSessionFactoryBean.setDataSource(dynamicDataSource);
            sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/**.xml"));//mapper文件
            return sqlSessionFactoryBean.getObject();
        }
    
    }
    
    

    2. 配置文件

    application.yml中:

    spring:
      datasource:
        dynamicNames: mv,test
        mv:
          url: jdbc:mysql:///movie?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true
          username: root
          password: root
          driver-class-name: com.mysql.jdbc.Driver
        test:
          url: jdbc:mysql:///testdb?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true
          username: root
          password: root
          driver-class-name: com.mysql.jdbc.Driver
        druid:
          db-type: com.alibaba.druid.pool.DruidDataSource
          initial-size: 10
          max-active: 100
          min-idle: 3
          max-wait: 5000
          pool-prepared-statements: true
          max-pool-prepared-statement-per-connection-size: 100
    

    3、自定以参数、继承类的参数

    3.1、这里先介绍一下AbstractRoutingSource中的参数:

    public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {
    
    	@Nullable
    	private Map<Object, Object> targetDataSources;//这个是目标数据源
    
    	@Nullable
    	private Object defaultTargetDataSource;//默认数据源
    
    	private boolean lenientFallback = true;
    
    	private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
    
    	@Nullable
    	private Map<Object, DataSource> resolvedDataSources;//已经解析的目标数据源
    
    	@Nullable
    	private DataSource resolvedDefaultDataSource;//解析的默认数据源
    

    在自定义类中参数,有默认数据源,和目标数据源:

     private Map<Object, Object> targetDataSources = new HashMap<>();//目标数据源
    
    private Object defaultTargetDataSource;//默认数据源
    

    4、需要生成SqlSessionFactory的实例,交给Spring容器管理

    数据源封装了,在AbstracRoutingSource中可以根据routeKey去解析过的resolveDataSources中根据key获取数据源,即到此可以获取数据源了。但是没有让进程之间规定谁去访问哪一个数据源,为了避免出现混乱,我们这里需要用到ThreadLocal

    三、用ThreadLocal 避免数据源访问混乱

    自定义一个类,专门去获取数据源的key,即A进程的访问不影响B进程,避免B进程不知去找哪一个key,出现数据混乱。

    如下代码

    public class DynamicDataSourceRouteKey {
    
        private static ThreadLocal<String> routeKey = new ThreadLocal<>();
        //获取数据源名称
        public static String getRouteKey() {
            return routeKey.get();
        }
        //设置数据源名称
        public static void setRouteKey(String type) {
            routeKey.set(type);
        }
        //清除数据源名
        public static void clearContextHolder(){
            routeKey.remove();
        }
    }
    

    从外访问接口:

    • 可以在拦截器中设置数据源名
    • 若有子进程,要在子进程中设置,在执行查询,指定routeKey

    到此使用AbstractRoutingSource 实现动态数据源的切换功能完成。

    代码地址master分支:

    github-hillheavy地址

    弯弯月亮,只为美好的自己。
  • 相关阅读:
    Gem命令详解
    使用Ruby脚本部署Redis Cluster集群
    解决gem install redis报错
    Redis Cluster命令安装
    Redis Sentinel原理介绍与部署
    PyCharm单行多行代码注释快捷键
    windows下python安装与卸载
    MySQL通过SQL语句查看表的索引
    Redis主从复制
    MySQL开启binlog日志
  • 原文地址:https://www.cnblogs.com/Choleen/p/14491835.html
Copyright © 2011-2022 走看看