Spring之多数据源切换的应用

这不是一个新的知识点扩展,顶多算是,Spring的AOP特性的一个应用。
那么下面开始今天的学习之旅!

场景

数据库读写分离,或者分库,总之多数据源的场景,怎么样实现自动切换(PS:不考虑各种分库分表的代理中间件噢)

使用

结合场景,那么我们的目的很简单。
就是利用Spring的AOP特性,创建一个注解类修饰service 方法,通过注解切入,设置数据库来源,完成调用后,再恢复原数据库来源。

那么我们需要怎么做

  • 注解类
  • AOP切面类,处理此注解的
  • 设置数据库来源的类

上面我们只是做到了,设置,查数据库之前,切换数据库来源。

  • 数据源信息在哪一步读取
  • 是怎么实现把切换后的数据源信息告诉dao层的

如果我们解决了以上问题,那么这个功能就应该可以轻松的实现咯。 这里就要提这次的重头戏

当在业务层需要涉及到查询多种同数据库的场景下,我们通常需要在执行sql的时候动态指定对应的datasource。 而Spring的AbstractRoutingDataSource则正好为我们提供了这一功能点。

先上例子吧,看完例子回后,我们再尝试着分析AbstractRoutingDataSource的来龙去脉看看能不能解答上面的2个疑点。

实例参考 这是别人的博文,这里我就不赘叙咯,免得文章拖沓,没有实质的意义。

这里仅仅贴我的核心类

public class DynamicDataSource extends AbstractRoutingDataSource {

    private static final Logger log = LoggerFactory.getLogger(DynamicDataSource.class);

    private static String dynamicPrefix = "spring.core.datasource";

    private static String filters = "Filters";

    private static String connectionProperties = "ConnectionProperties";
    @Override
    protected Object determineCurrentLookupKey() {
        log.debug("数据源为{}", DataSourceContextHolder.getDB());
        return DataSourceContextHolder.getDB();
    }

    /**
     * 构建动态多数据源
     * @param route
     * @param prefix
     * @return
     */
    public static DynamicDataSource doCreateDataSource(String route, String prefix){
        if(StringUtils.isEmpty(route)){
            throw new IllegalArgumentException("create route dbSource is error; route is null");
        }
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        String[] dataNames = route.split(",");
        if(null == dataNames || dataNames.length == 0){
            throw new IllegalArgumentException("create route dbSource is error; Route is format error, please refer to dataSource1,dataSource2");
        }
        Map<Object, Object> dsMap = new HashMap(10);
        //遍历数据源
        String defaultDataName = null;
        for(String dataName : dataNames){
            if(StringUtils.isEmpty(defaultDataName)){
                defaultDataName = dataName;
            }
            //得到当前数据源配置
            Map<String, Object> paramMap = getDataSourceProperties(prefix, dataName);
            if(CollectionUtils.isEmpty(paramMap)){
                throw new CloudException("init database is error,  param empty");
            }
            DruidDataSource druidDataSource = (DruidDataSource) DataSourceBuilder.create().type(DruidDataSource.class).build();
            //将配置参数,注入druidDataSource对象
            new RelaxedDataBinder(druidDataSource).bind(new MutablePropertyValues(paramMap));
            //设置数据源名称
            druidDataSource.setName(dataName);
            try {
                //初始化数据源
                druidDataSource.init();
            } catch (SQLException e) {
                log.error("init database {} is error " + e.getMessage(), dataName, e);
                throw new CloudException("init" +dataName +"db is error");
            }
            dsMap.put(dataName, druidDataSource);
        }
        //首个数据源为默认
        dynamicDataSource.setDefaultTargetDataSource(dsMap.get(defaultDataName));
        dynamicDataSource.setTargetDataSources(dsMap);

        return dynamicDataSource;
    }



    /**
     * 根据系统参数获取数据源配置
     * @param prefix
     * @param dataName
     * @return
     */
    private static Map<String, Object> getDataSourceProperties(String prefix, String dataName){
        Map<String, Object> paramMap = new HashMap<>();
        //
        Properties properties = GlobalRuntimeConfigFactory.getInstance().getProperties();
        //prefix=spring.datasource 共有配置获取
        doIterator(properties, prefix, paramMap);
        prefix = dynamicPrefix + "." + dataName;
      
        doIterator(properties, prefix, paramMap);
        return paramMap;
    }

    /**
     * 遍历配置对象launchArgs,将需要配置存入paramMap
     * @param properties 配置对象
     * @param prefix 需要的配置前缀
     * @param paramMap 返回需要的配置
     */
    private static void doIterator(Properties properties, String prefix, Map<String, Object> paramMap){
        if(null == properties ){
            return;
        }
        //launchArg格式  --key=value
        for(Map.Entry<Object,Object> entry: properties.entrySet()){
            if(StringUtils.isEmpty(entry)){
                continue;
            }
            String keyName = entry.getKey().toString();
            String value = entry.getValue().toString();
            if (!StringUtils.isEmpty(keyName) && keyName.startsWith(prefix)) {
                String key = StringUtils.toUpperCaseFirstOne(keyName.substring(prefix.length() + 1));
                if (!key.contains(".")) {
                    paramMap.put(StringUtils.toUpperCaseFirstOne(keyName.substring(prefix.length() + 1)), value);
                }
            }
        }
    }
}

 @Value("${spring.datasource.route:}")
    private String route;

    public static final String PREFIX = "spring.datasource";
    
    
 @Bean
    @Primary
    @ConfigurationProperties(prefix = DataSourceConfiguration.PREFIX)
    @ConditionalOnMissingClass("spring.framework.jta.configuration.PrimaryXADataSourceConfiguration")
    public DataSource dataSource() {
        if(StringUtils.isEmpty(route)){
            return DataSourceBuilder.create().type(DruidDataSource.class).build();
            //        return DataSourceBuilder.create().type(SweetDataSource.class).build();
        } else {
            //启动动态多数据源
            return DynamicDataSource.doCreateDataSource(route, PREFIX);
        }
    }

原理

原理,不是我总计的,还是上面那个文章的博主的原话吧

AbstractRoutingDataSource 动态路由数据源的注入原理,可以看到这个内部类里面包含了多种用于做数据源映射的map数据结构

  • private Map<Object, DataSource> resolvedDataSources;

也就是上边我们所提及的使用于查询当前数据源key的方法

  • protected abstract Object determineCurrentLookupKey();

而在该类的afterPropertiesSet里面,又有对于初始化数据源的注入操作,这里面的targetDataSources 正是上文中我们对在初始化数据源时候注入的信息

@Override
	public void afterPropertiesSet() {
		if (this.targetDataSources == null) {
			throw new IllegalArgumentException("Property 'targetDataSources' is required");
		}
		this.resolvedDataSources = new HashMap<Object, DataSource>(this.targetDataSources.size());
		for (Map.Entry<Object, Object> entry : this.targetDataSources.entrySet()) {
			Object lookupKey = resolveSpecifiedLookupKey(entry.getKey());
			DataSource dataSource = resolveSpecifiedDataSource(entry.getValue());
			this.resolvedDataSources.put(lookupKey, dataSource);
		}
		if (this.defaultTargetDataSource != null) {
			this.resolvedDefaultDataSource = resolveSpecifiedDataSource(this.defaultTargetDataSource);
		}
	}
posted @ 2020-06-08 14:57  谢幕ゾ华丽  阅读(445)  评论(0编辑  收藏  举报