zoukankan      html  css  js  c++  java
  • springboot集成多数据源

    简要原理:

    1)DataSourceEnum列出所有的数据源的key---key

    2)DataSourceHolder是一个线程安全的DataSourceEnum容器,并提供了向其中设置和获取DataSourceEnum的方法

    3)DynamicDataSource继承AbstractRoutingDataSource并重写其中的方法determineCurrentLookupKey(),在该方法中使用DataSourceHolder获取当前线程的DataSourceEnum

    4)MyBatisConfig中生成2个数据源DataSource的bean---value

    5)MyBatisConfig中将1)和4)组成的key-value对写入到DynamicDataSource动态数据源的targetDataSources属性(当然,同时也会设置2个数据源其中的一个为DynamicDataSource的defaultTargetDataSource属性中)

    6)将DynamicDataSource作为primary数据源注入到SqlSessionFactory的dataSource属性中去,并且该dataSource作为transactionManager的入参来构造DataSourceTransactionManager

    7)使用spring aop根据不同的包设置不同的数据源(DataSourceExchange),先使用DataSourceHolder设置将要使用的数据源key

    注意:在mapper层进行操作的时候,会先调用determineCurrentLookupKey()方法获取一个数据源(获取数据源:先根据设置去targetDataSources中去找,若没有,则选择defaultTargetDataSource),之后在进行数据库操作。

    DataSourceEnum

    package com.theeternity.common.dataSource;
    
    /**
     * @program: boxApi
     * @description: 多数据源枚举类
     * @author: tonyzhang
     * @create: 2018-12-18 11:14
     */
    public enum DataSourceEnum {
        /**
         * @Description: DS1数据源1, DS2数据源2
         * @Param:
         * @return:
         * @Author: tonyzhang
         * @Date: 2018-12-18 11:20
         */
        DS1("ds1"), DS2("ds2");
    
        private String key;
    
        public String getKey() {
            return key;
        }
    
        public void setKey(String key) {
            this.key = key;
        }
    
        DataSourceEnum(String key) {
            this.key = key;
        }
    }
    

    DataSourceHolder

    作用:构建一个DatabaseType容器,并提供了向其中设置和获取DataSourceEnmu的方法

    package com.theeternity.common.dataSource;
    
    /**
     * @program: boxApi
     * @description: DynamicDataSourceHolder用于持有当前线程中使用的数据源标识
     * @author: tonyzhang
     * @create: 2018-12-18 11:16
     */
    public class DataSourceHolder {
        private static final ThreadLocal<String> dataSources = new ThreadLocal<String>();
    
        public static void setDataSources(String dataSource) {
            dataSources.set(dataSource);
        }
    
        public static String getDataSources() {
            return dataSources.get();
        }
    }
    
    

    DynamicDataSource

    作用:使用DatabaseContextHolder获取当前线程的DataSourceEnmu

    package com.theeternity.common.dataSource;
    
    import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
    
    /**
     * @program: boxApi
     * @description: DynamicDataSource的类,继承AbstractRoutingDataSource并重写determineCurrentLookupKey方法
     * @author: tonyzhang
     * @create: 2018-12-18 11:17
     */
    public class DynamicDataSource extends AbstractRoutingDataSource {
        @Override
        protected Object determineCurrentLookupKey() {
            return DataSourceHolder.getDataSources();
        }
    }
    
    

    MyBatisConfig

    作用:
    通过读取application-test.yml文件生成两个数据源(writeDS、readDS)
    使用以上生成的两个数据源构造动态数据源dataSource
    @Primary:指定在同一个接口有多个实现类可以注入的时候,默认选择哪一个,而不是让@Autowire注解报错(一般用于多数据源的情况下)
    @Qualifier:指定名称的注入,当一个接口有多个实现类的时候使用(在本例中,有两个DataSource类型的实例,需要指定名称注入)
    @Bean:生成的bean实例的名称是方法名(例如上边的@Qualifier注解中使用的名称是前边两个数据源的方法名,而这两个数据源也是使用@Bean注解进行注入的)
    通过动态数据源构造SqlSessionFactory和事务管理器(如果不需要事务,后者可以去掉)

    package com.theeternity.beans.mybatisConfig;
    
    import com.theeternity.common.dataSource.DataSourceEnum;
    import com.theeternity.common.dataSource.DynamicDataSource;
    import org.apache.ibatis.session.SqlSessionFactory;
    import org.mybatis.spring.SqlSessionFactoryBean;
    import org.springframework.beans.factory.annotation.Qualifier;
    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.context.annotation.Primary;
    import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
    import org.springframework.jdbc.datasource.DataSourceTransactionManager;
    
    import javax.sql.DataSource;
    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * @program: ApiBoot
     * @description: 动态数据源配置
     * @author: TheEternity Zhang
     * @create: 2019-02-18 11:04
     */
    @Configuration
    public class MyBatisConfig {
    
        @Value("${spring.datasource.type}")
        private Class<? extends DataSource> dataSourceType;
    
        @Value("${mybatis.type-aliases-package}")
        private String basicPackage;
    
        @Value("${mybatis-plus.mapper-locations}")
        private String mapperLocation;
    
    
        @Bean(name="writeDS")
        @ConfigurationProperties(prefix = "primary.datasource.druid")
        public DataSource writeDataSource() {
            return DataSourceBuilder.create().type(dataSourceType).build();
        }
        /**
         * 有多少个从库就要配置多少个
         * @return
         */
        @Bean(name = "readDS")
        @ConfigurationProperties(prefix = "back.datasource.druid")
        public DataSource readDataSourceOne(){
            return DataSourceBuilder.create().type(dataSourceType).build();
        }
    
    
        /**
         * @Primary 该注解表示在同一个接口有多个实现类可以注入的时候,默认选择哪一个,而不是让@autowire注解报错
         * @Qualifier 根据名称进行注入,通常是在具有相同的多个类型的实例的一个注入(例如有多个DataSource类型的实例)
         */
        @Bean
        @Primary
        public DynamicDataSource dataSource(@Qualifier("writeDS") DataSource writeDS,
                                            @Qualifier("readDS") DataSource readDS) {
            Map<Object, Object> targetDataSources = new HashMap<>();
            targetDataSources.put(DataSourceEnum.DS1.getKey(), writeDS);
            targetDataSources.put(DataSourceEnum.DS2.getKey(), readDS);
    
            DynamicDataSource dataSource =new DynamicDataSource();
            // 该方法是AbstractRoutingDataSource的方法
            dataSource.setTargetDataSources(targetDataSources);
            // 默认的datasource设置为writeDS
            dataSource.setDefaultTargetDataSource(writeDS);
    
            return dataSource;
        }
    
        /**
         * 根据数据源创建SqlSessionFactory
         */
        @Bean
        public SqlSessionFactory sqlSessionFactory(DynamicDataSource dataSource) throws Exception {
            SqlSessionFactoryBean fb = new SqlSessionFactoryBean();
            // 指定数据源(这个必须有,否则报错)
            fb.setDataSource(dataSource);
            // 下边两句仅仅用于*.xml文件,如果整个持久层操作不需要使用到xml文件的话(只用注解就可以搞定),则不加
            // 指定基包
            fb.setTypeAliasesPackage(basicPackage);
            fb.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocation));
            return fb.getObject();
        }
    
        /**
         * 配置事务管理器
         */
        @Bean
        public DataSourceTransactionManager transactionManager(DynamicDataSource dataSource) throws Exception {
            return new DataSourceTransactionManager(dataSource);
        }
    }
    
    

    DataSourceExchange

    package com.theeternity.common.aop;
    
    import com.theeternity.common.dataSource.DataSourceEnum;
    import com.theeternity.common.dataSource.DataSourceHolder;
    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.aspectj.lang.annotation.Pointcut;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Component;
    
    /**
     * @program: boxApi
     * @description: 数据源自动切换AOP
     * @author: tonyzhang
     * @create: 2018-12-18 11:20
     */
    @Aspect
    @Component
    public class DataSourceExchange {
    
        private Logger logger= LoggerFactory.getLogger(DataSourceExchange.class);
    
        @Pointcut("execution(* com.theeternity.core.*.service..*(..))")
        public void pointcut(){}
    
        /** 
         * @Description: 在service方法开始之前切换数据源
         * @Param: [joinPoint] 
         * @return: void 
         * @Author: tonyzhang 
         * @Date: 2018-12-18 11:28
         */ 
        @Before(value="pointcut()")
        public void before(JoinPoint joinPoint){
            //获取目标对象的类类型
            Class<?> aClass = joinPoint.getTarget().getClass();
            String c = aClass.getName();
            System.out.println("作用包名:"+c);
            String[] ss = c.split("\.");
            //获取包名用于区分不同数据源
            String packageName = ss[3];
            System.out.println("包名:"+packageName);
            if ("AutoGenerator".equals(packageName)) {
                DataSourceHolder.setDataSources(DataSourceEnum.DS1.getKey());
                logger.info("数据源:"+DataSourceEnum.DS1.getKey());
            } else {
                DataSourceHolder.setDataSources(DataSourceEnum.DS2.getKey());
                logger.info("数据源:"+DataSourceEnum.DS2.getKey());
            }
        }
    
        /** 
         * @Description: 执行完毕之后将数据源清空 
         * @Param: [joinPoint] 
         * @return: void 
         * @Author: tonyzhang 
         * @Date: 2018-12-18 11:27
         */ 
        @After(value="pointcut()")
        public void after(JoinPoint joinPoint){
            DataSourceHolder.setDataSources(null);
    
        }
    }
    
    

    屏蔽springboot自带的自动注册数据源

    很多朋友反映遇到数据源循环依赖的问题,可以试一下将MyBatisConfig中的相关代码换成这样试试

    首先要将spring boot自带的DataSourceAutoConfiguration禁掉,因为它会读取application.properties文件的spring.datasource.*属性并自动配置单数据源。在@SpringBootApplication注解中添加exclude属性即可:

    package com.theeternity.core;
    
    import org.mybatis.spring.annotation.MapperScan;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
    
    @SpringBootApplication(scanBasePackages = "com.theeternity",exclude = {DataSourceAutoConfiguration.class})
    /**
     * 全局配置,扫描指定包下的dao接口,不用每个dao接口上都写@Mapper注解了
     */
    @MapperScan("com.theeternity.core.*.dao")
    public class CoreApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(CoreApplication.class, args);
        }
    
    }
    

    如果不屏蔽DataSourceAutoConfiguration可以使用如下测试一下(待测试)

    将MyBatisConfig中SqlSessionFactory的构建方法改为下面的

     @Bean
    2     public SqlSessionFactory sqlSessionFactory(@Qualifier("writeDS") DataSource writeDS,
    3                                                @Qualifier("readDS") DataSource readDS) throws Exception{
    4         SqlSessionFactoryBean fb = new SqlSessionFactoryBean();
    5         fb.setDataSource(this.dataSource(writeDS, readDS));
    6         fb.setTypeAliasesPackage(basicPackage);
              fb.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocation));
    8         return fb.getObject();
    9     }
    

    主配置文件

    #配置使用的文件
    spring:
      profiles:
        active: test,redis
      devtools:
        restart:
          enabled: true
          additional-paths: src/main/java
    #配置tomcat端口及路径
    server:
      port: 8088
      servlet:
        context-path: /core
    #mybatis配置
    mybatis:
      mapper-locations: classpath*:/mybatis-mapper/*.xml
      type-aliases-package: com.theeternity.core.*.entity
      configuration:
        map-underscore-to-camel-case: true #驼峰命名
    #mybatis plus配置
    mybatis-plus:
      mapper-locations: classpath*:/mybatis-plus-mapper/*.xml
      # MyBaits 别名包扫描路径,通过该属性可以给包中的类注册别名,注册后在 Mapper 对应的 XML 文件中可以直接使用类名,而不用使用全限定的类名
      type-aliases-package: com.theeternity.core.*.entity
      # 数据库表与实体类的驼峰命名自动转换
      configuration:
        map-underscore-to-camel-case: true
    

    配置文件application-test.yml

    spring:
      datasource:
        #使用druid连接池
        type: com.alibaba.druid.pool.DruidDataSource
    
    # 自定义的主数据源配置信息
    primary:
      datasource:
        #druid相关配置
        druid:
          #监控统计拦截的filters
          filters: stat
          driverClassName: com.mysql.cj.jdbc.Driver
          #配置基本属性
          url: jdbc:mysql://localhost:3306/wechatMVC?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&useSSL=false
          username: ***
          password: ***
          #配置初始化大小/最小/最大
          initialSize: 1
          minIdle: 1
          maxActive: 20
          #获取连接等待超时时间
          maxWait: 60000
          #间隔多久进行一次检测,检测需要关闭的空闲连接
          timeBetweenEvictionRunsMillis: 60000
          #一个连接在池中最小生存的时间
          minEvictableIdleTimeMillis: 300000
          validationQuery: SELECT 'x'
          testWhileIdle: true
          testOnBorrow: false
          testOnReturn: false
          #打开PSCache,并指定每个连接上PSCache的大小。oracle设为true,mysql设为false。分库分表较多推荐设置为false
          poolPreparedStatements: false
          maxPoolPreparedStatementPerConnectionSize: 20
    
    # 自定义的从数据源配置信息
    back:
      datasource:
        #druid相关配置
        druid:
          #监控统计拦截的filters
          filters: stat
          driverClassName: com.mysql.cj.jdbc.Driver
          #配置基本属性
          url: jdbc:mysql://localhost:3306/mycrm?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&useSSL=false
          username: ***
          password: ***
          #配置初始化大小/最小/最大
          initialSize: 1
          minIdle: 1
          maxActive: 20
          #获取连接等待超时时间
          maxWait: 60000
          #间隔多久进行一次检测,检测需要关闭的空闲连接
          timeBetweenEvictionRunsMillis: 60000
          #一个连接在池中最小生存的时间
          minEvictableIdleTimeMillis: 300000
          validationQuery: SELECT 'x'
          testWhileIdle: true
          testOnBorrow: false
          testOnReturn: false
          #打开PSCache,并指定每个连接上PSCache的大小。oracle设为true,mysql设为false。分库分表较多推荐设置为false
          poolPreparedStatements: false
          maxPoolPreparedStatementPerConnectionSize: 20
    

    参考文档:

    https://www.cnblogs.com/java-zhao/p/5413845.html (主参考流程)

    http://www.cnblogs.com/java-zhao/p/5415896.html (转aop更改数据源)

    https://blog.csdn.net/maoyeqiu/article/details/74011626 (将datasource注入bean简单方法)

    https://blog.csdn.net/neosmith/article/details/61202084(将spring boot自带的DataSourceAutoConfiguration禁掉)

  • 相关阅读:
    Zookeeper 入门第一篇
    jmap命令
    Java中的原子操作类
    Fel表达式实践
    Fel表达式使用过程中需要注意的问题
    【luoguP1196】 [NOI2002]银河英雄传说--边带权并查集 ,
    【luoguP1955 】[NOI2015]程序自动分析--普通并查集
    【csp模拟赛2】 序列操作
    【csp模拟赛2】 爆搜 方格加数
    HZWER
  • 原文地址:https://www.cnblogs.com/eternityz/p/12241413.html
Copyright © 2011-2022 走看看