• 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);
    		}
    	}
  • 相关阅读:
    mailto发送邮件
    使用css实现一个持续的动画效果
    documentFragment添加节点
    删除数组的第一个元素,不要直接修改数组,结果返回新的数组
    js数组去重
    css定位position(侧边栏导航)
    mongoexport导出mongodb数据库中的数据
    textarea头部不顶行问题和textarea禁止拉伸
    HTML meta标签
    textarea
  • 原文地址:https://www.cnblogs.com/Profound/p/13065707.html
Copyright © 2020-2023  润新知