• SpringBoot集成多数据源-----基于数据库维护


    一、需求背景

    最近团队需要做一个需求,可能会从多个数据源中抽取数据,然后经过清洗、转换等生成统计报告。因此我们的项目需要对接多个数据源,并且需要满足以下要求:

    1、多个请求同时到达,每个请求可能访问不同的数据库,请求间应该隔离,不能阻塞

    2、数据源信息能做到好维护,并且支持动态添加(添加之后,代码能感应到)

    二、方案设计

     A、AbstractRoutingDataSource

    spring中abstract的类大部分都是可扩展的,在spring中操作数据源一般都是要基于orm框架,例如mybatis啥的,模型如下:

     但是现在会有多个数据源,一种比较好点的方式是多个数据源共用一个sessionFactory,如下:

     这样后面就算是增加数据源,变化也是非常小的。

    再说回到abstractRoutingDataSource,可以理解为就是一个数据源的容器,里面分成了默认数据源还有外部数据源,外部数据源通过Map维护,通过数据源名称可以找到对应的数据源。

    B、AOP

    系统启动的时候,加载默认数据库(项目自己的数据源,非外部数据源)。等到有请求进来的时候,通过aop拦截判断数据源是否已加载,若未加载,加载一次

    三、代码实现

    A、数据表结构

    B、数据源配置

    package com.yunzhangfang.platform.dataplatform.dw.service.datasource.dynamic;
    
    import com.yunzhangfang.platform.dataplatform.dw.service.datasource.holder.DynamicDataSourceContextHolder;
    import com.yunzhangfang.platform.dataplatform.dw.service.domain.DatasourceConfigDO;
    import com.yzf.accounting.common.exception.BizRuntimeException;
    import lombok.Data;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.commons.collections.CollectionUtils;
    import org.apache.commons.lang.StringUtils;
    import org.springframework.boot.jdbc.DataSourceBuilder;
    import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
    
    import javax.sql.DataSource;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    /**
     * 动态数据源
     */
    @Slf4j
    @Data
    public class DynamicDataSource extends AbstractRoutingDataSource {
    
        // 默认数据源
        private Object defaultTargetDataSource;
    
        // 外部数据源
        private Map<Object, Object> targetDataSources;
    
        @Override
        protected Object determineCurrentLookupKey() {
            String datasource = DynamicDataSourceContextHolder.getDataSource();
            if(StringUtils.isBlank(datasource)) {
                log.info("默认数据源");
                return datasource;
            }
    
            if(!this.targetDataSources.containsKey(datasource)) {
                throw new BizRuntimeException("不存在此数据源");
            }
    
            return datasource;
        }
    
        /**
         * 设置默认数据源
         * @param defaultTargetDataSource
         */
        @Override
        public void setDefaultTargetDataSource(Object defaultTargetDataSource) {
            super.setDefaultTargetDataSource(defaultTargetDataSource);
            this.defaultTargetDataSource = defaultTargetDataSource;
        }
    
        /**
         * 设置外部数据源
         * @param targetDataSources
         */
        @Override
        public void setTargetDataSources(Map<Object, Object> targetDataSources) {
            super.setTargetDataSources(targetDataSources);
            this.targetDataSources = targetDataSources;
        }
    
        /**
         * 创建数据源
         * @return
         */
        public boolean createDataSource(List<DatasourceConfigDO> datasourceConfigList) {
            if(CollectionUtils.isEmpty(datasourceConfigList)) {
                return false;
            }
    
            Map<Object, Object> dataSourceMap = new HashMap<>();
            datasourceConfigList.forEach(datasourceConfig -> {
                DataSource dataSource = null;
                try {
                    dataSource = DataSourceBuilder.create()
                            .driverClassName(datasourceConfig.getDsDriver())
                            .url(datasourceConfig.getDsUrl())
                            .username(datasourceConfig.getDsUsername())
                            .password(datasourceConfig.getDsPassword())
                            .type((Class<? extends DataSource>) Class.forName(datasourceConfig.getDsType()))
                            .build();
                } catch (ClassNotFoundException e) {
                    log.error("创建数据源出现异常", e);
                }
                dataSourceMap.put(datasourceConfig.getDsName(), dataSource);
            });
            this.targetDataSources.putAll(dataSourceMap);
            setTargetDataSources(this.targetDataSources);
            super.afterPropertiesSet();
            log.info("数据源创建成功");
            return true;
        }
    }
    package com.yunzhangfang.platform.dataplatform.dw.service.datasource.config;
    
    import com.yunzhangfang.platform.dataplatform.dw.service.datasource.dynamic.DynamicDataSource;
    import org.apache.ibatis.session.SqlSessionFactory;
    import org.mybatis.spring.SqlSessionFactoryBean;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.boot.context.properties.ConfigurationProperties;
    import org.springframework.boot.jdbc.DataSourceBuilder;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
    import org.springframework.core.io.support.ResourcePatternResolver;
    
    import javax.sql.DataSource;
    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * @author 阿里-马云
     * @date 2021/6/18 11:52
     */
    @Configuration
    public class DataSourceConfig {
    
        @Value("${mybatis.type-aliases-package}")
        private String typeAliasesPackage;
    
        @Value("${mybatis.mapperLocations}")
        private String mapperLocations;
    
        @Bean(name = "primarySource")
        @ConfigurationProperties(prefix = "spring.datasource")
        public DataSource dataSource() {
            return DataSourceBuilder.create().build();
        }
    
        @Bean(name = "dynamicDataSource")
        public DynamicDataSource dynamicDataSource() {
            DynamicDataSource dynamicDataSource = new DynamicDataSource();
            // 配置缺省的数据源
            // 默认数据源配置 DefaultTargetDataSource
            dynamicDataSource.setDefaultTargetDataSource(dataSource());
            Map<Object, Object> targetDataSources = new HashMap<>();
            // 额外数据源配置 TargetDataSources
            targetDataSources.put("primarySource", dataSource());
            dynamicDataSource.setTargetDataSources(targetDataSources);
            return dynamicDataSource;
        }
    
        @Bean
        public SqlSessionFactory sqlSessionFactory() throws Exception {
            SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
            sqlSessionFactoryBean.setDataSource(dynamicDataSource());
            // 设置mybatis的主配置文件
            ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
            // 设置别名包
            sqlSessionFactoryBean.setTypeAliasesPackage(typeAliasesPackage);
            // 手动配置mybatis的mapper.xml资源路径,如果单纯使用注解方式,不需要配置该行
            sqlSessionFactoryBean.setMapperLocations(resolver.getResources(mapperLocations));
            return sqlSessionFactoryBean.getObject();
        }
    }

    C、AOP切面

    package com.yunzhangfang.platform.dataplatform.dw.service.datasource.aspect;
    
    import com.yunzhangfang.platform.dataplatform.dw.service.datasource.annotation.TargetDataSource;
    import com.yunzhangfang.platform.dataplatform.dw.service.datasource.dynamic.DynamicDataSource;
    import com.yunzhangfang.platform.dataplatform.dw.service.datasource.holder.DynamicDataSourceContextHolder;
    import com.yunzhangfang.platform.dataplatform.dw.service.datasource.init.MultipleDatasourcesIniter;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.commons.lang.StringUtils;
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.annotation.After;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    
    /**
     * @author 阿里-马云
     * @date 2021/6/18 9:47
     */
    @Aspect
    @Component
    @Slf4j
    public class DynamicDattaSourceAspect {
    
        @Autowired
        private MultipleDatasourcesIniter multipleDatasourcesIniter;
    
        @Autowired
        private DynamicDataSource dynamicDataSource;
    
        //改变数据源
        @Before("@annotation(targetDataSource)")
        public void determineDataSource(JoinPoint joinPoint, TargetDataSource targetDataSource) {
            String dsName = targetDataSource.name();
            if(StringUtils.isBlank(dsName)) {
                // 若数据源名称未配置,则走默认数据源
                log.info("使用默认数据源");
            } else {
                // 判断此数据源是否已被加载
                if(!dynamicDataSource.getTargetDataSources().containsKey(dsName)) {
              // init方法为从数据库中获取配置信息 dynamicDataSource.createDataSource(multipleDatasourcesIniter.init()); } DynamicDataSourceContextHolder.setDataSource(dsName); log.info(
    "使用外部数据源,数据源为:{}", dsName); } } @After("@annotation(targetDataSource)") public void clearDataSource(JoinPoint joinPoint, TargetDataSource targetDataSource) { log.info("清除数据源 " + targetDataSource.name() + " !"); DynamicDataSourceContextHolder.clearDataSource(); } }

    D、DynamicDataSourceContextHolder

    package com.yunzhangfang.platform.dataplatform.dw.service.datasource.holder;
    
    /**
     * 动态数据源上下文管理
     */
    public class DynamicDataSourceContextHolder {
    
        /**
         * 存放当前线程使用的数据源类型信息
         */
        private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
    
        /**
         * 设置数据源
         * @param dataSource
         */
        public static void setDataSource(String dataSource) {
            contextHolder.set(dataSource);
        }
    
        /**
         * 获取数据源
         * @return
         */
        public static String getDataSource() {
            return contextHolder.get();
        }
    
        /**
         * 清除数据源
         */
        public static void clearDataSource() {
            contextHolder.remove();
        }
    }

    E、注解

    package com.yunzhangfang.platform.dataplatform.dw.service.datasource.annotation;
    
    import java.lang.annotation.*;
    
    /**
     * 运行时生效,可作用于类以及方法上
     * 规范:此注解只应用在repository上,一个repository只允许访问一个数据源
     * 使用方法:将此注解标注在repository层,属性name标识数据源名称(dsName),dsName相关信息维护在表yzf_datasource_config
     */
    @Target({ElementType.TYPE, ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface TargetDataSource {
    
        String name() default "";
    }

    F、使用方法

    默认数据源可以直接不配置,如果是其他数据源,需要在repository层手动标注数据源名称,如下:

    @Component
    public class DictDataRepository {
    
        @Autowired
        private SysDictDataMapper dictDataMapper;
    
        /**
         * 根据字典类型查询字典数据
         *
         * @param dictType
         * @return
         */
        @TargetDataSource(name = "baUser")
        public List<SysDictData> queryDictDataByType(String dictType) {
            // operate db
        }
    }
    如果想给予我更多的鼓励,求打

    因为,我的写作热情也离不开您的肯定支持,感谢您的阅读,我是【阿里马云】!

  • 相关阅读:
    (单例)使用同步基元变量来检测程序是否已运行
    使用委托解决方法的跨线程调用问题
    Rtmp/Hls直播、点播服务器部署与配置
    关于C#调用广州医保HG_Interface.dll调用的一些总结(外部组件异常)
    redhat7.3配置163 yum源
    模块化InnoSetup依赖项安装
    [迷宫中的算法实践]迷宫生成算法——递归分割算法
    [新手学Java]使用beanUtils控制javabean
    【HTML5】Canvas绘图详解-1
    【Swift 】- 闭包
  • 原文地址:https://www.cnblogs.com/alimayun/p/14912356.html
Copyright © 2020-2023  润新知