• springboot动态切换数据源


    1、创建一个springboot项目,导入依赖(3.3.0)
     <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
                <version>3.6.1</version>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>3.0.3</version>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-aop</artifactId>
            </dependency>
    
            <!--Druid连接池-->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid-spring-boot-starter</artifactId>
                <version>1.2.3</version>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-devtools</artifactId>
                <scope>runtime</scope>
                <optional>true</optional>
            </dependency>
            <dependency>
                <groupId>com.mysql</groupId>
                <artifactId>mysql-connector-j</artifactId>
                <scope>runtime</scope>
            </dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <optional>true</optional>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
        </dependencies>
    
    2、实现ThreadLocal

    创建一个类用于实现ThreadLocal,主要是通过get,set,remove方法来获取、设置、删除当前线程对应的数据源。

    public class DataSourceContextHolder {
        //此类提供线程局部变量。这些变量不同于它们的正常对应关系是每个线程访问一个线程(通过get、set方法),有自己的独立初始化变量的副本。
        private static final ThreadLocal<String> DATASOURCE_HOLDER = new ThreadLocal<>();
        /**
         * 设置数据源
         * @param dataSourceName 数据源名称
         */
        public static void setDataSource(String dataSourceName){
            DATASOURCE_HOLDER.set(dataSourceName);
        }
        /**
         * 获取当前线程的数据源
         * @return 数据源名称
         */
        public static String getDataSource(){
            return DATASOURCE_HOLDER.get();
        }
        /**
         * 删除当前数据源
         */
        public static void removeDataSource(){
            DATASOURCE_HOLDER.remove();
        }
    }
    
    3、实现AbstractRoutingDataSource

    定义一个动态数据源类实现AbstractRoutingDataSource,通过determineCurrentLookupKey方法与上述实现的ThreadLocal类中的get方法进行关联,实现动态切换数据源。

    /**
     * @description: 实现动态数据源,根据AbstractRoutingDataSource路由到不同数据源中
     * @date: 2023/7/27 11:18
     **/
    public class DynamicDataSource extends AbstractRoutingDataSource {
        public DynamicDataSource(DataSource defaultDataSource,Map<Object, Object> targetDataSources){
            super.setDefaultTargetDataSource(defaultDataSource);
            super.setTargetDataSources(targetDataSources);
        }
        @Override
        protected Object determineCurrentLookupKey() {
            return DataSourceContextHolder.getDataSource();
        }
    }
    

    上述代码中,还实现了一个动态数据源类的构造方法,主要是为了设置默认数据源,以及以Map保存的各种目标数据源。其中Map的key是设置的数据源名称,value则是对应的数据源(DataSource)。

    4、配置数据库

    application.yml中配置数据库信息:

    #设置数据源
    spring:
      datasource:
        type: com.alibaba.druid.pool.DruidDataSource
        druid:
          master:
            url: jdbc:mysql://xxxxxx:3306/test1?characterEncoding=utf-8&allowMultiQueries=true&zeroDateTimeBehavior=convertToNull&useSSL=false
            username: root
            password: 123456
            driver-class-name: com.mysql.cj.jdbc.Driver
          slave:
            url: jdbc:mysql://xxxxx:3306/test2?characterEncoding=utf-8&allowMultiQueries=true&zeroDateTimeBehavior=convertToNull&useSSL=false
            username: root
            password: 123456
            driver-class-name: com.mysql.cj.jdbc.Driver
          initial-size: 15
          min-idle: 15
          max-active: 200
          max-wait: 60000
          time-between-eviction-runs-millis: 60000
          min-evictable-idle-time-millis: 300000
          validation-query: ""
          test-while-idle: true
          test-on-borrow: false
          test-on-return: false
          pool-prepared-statements: false
          connection-properties: false
    mybatis:
      mapper-locations: mappers/**/*.xml
      configuration:
        map-underscore-to-camel-case: true
        log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    

    配置类

    /**
     * @description: 设置数据源
     * @date: 2023/7/27 11:34
     **/
    @Configuration
    public class DateSourceConfig {
        @Bean
        @ConfigurationProperties("spring.datasource.druid.master")
        public DataSource masterDataSource(){
            return DruidDataSourceBuilder.create().build();
        }
        @Bean
        @ConfigurationProperties("spring.datasource.druid.slave")
        public DataSource slaveDataSource(){
            return DruidDataSourceBuilder.create().build();
        }
        @Bean(name = "dynamicDataSource")
        @Primary
        public DynamicDataSource createDynamicDataSource(){
            Map<Object,Object> dataSourceMap = new HashMap<>();
            DataSource defaultDataSource = masterDataSource();
            dataSourceMap.put("master",defaultDataSource);
            dataSourceMap.put("slave",slaveDataSource());
            return new DynamicDataSource(defaultDataSource,dataSourceMap);
        }
    }
    

    通过配置类,将配置文件中的配置的数据库信息转换成datasource,并添加到DynamicDataSource中,同时通过@Bean将DynamicDataSource注入Spring中进行管理,后期在进行动态数据源添加时,会用到。

    5、测试
    @GetMapping("/getData.do/{datasourceName}")
    public String getMasterData(@PathVariable("datasourceName") String datasourceName){
        DataSourceContextHolder.setDataSource(datasourceName);
        TestUser testUser = testUserMapper.selectOne(null);
        DataSourceContextHolder.removeDataSource();
        return testUser.getUserName();
    }
    

    通过执行结果,我们看到传递不同的数据源名称,查询对应的数据库是不一样的,返回结果也不一样。
    在上述代码中,我们看到DataSourceContextHolder.setDataSource(datasourceName); 来设置了当前线程需要查询的数据库,通过DataSourceContextHolder.removeDataSource(); 来移除当前线程已设置的数据源。

    注:启动程序时,小伙伴不要忘记将SpringBoot自动添加数据源进行排除哦,否则会报循环依赖问题。

    @SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
    
    6、优化调整(注解切换数据源)

    在上述中,虽然已经实现了动态切换数据源,但是我们会发现如果涉及到多个业务进行切换数据源的话,我们就需要在每一个实现类中添加这一段代码。
    说到这有小伙伴应该就会想到使用注解来进行优化,接下来我们来实现一下。

    6.1 定义注解

    我们就用mybatis动态数据源切换的注解:DS,代码如下:

    @Target({ElementType.METHOD,ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    public @interface DS {
        String value() default "master";
    }
    
    6、2 实现aop
    @Aspect
    @Component
    @Slf4j
    public class DSAspect {    @Pointcut("@annotation(com.jiashn.dynamic_datasource.dynamic.aop.DS)")
        public void dynamicDataSource(){}
        @Around("dynamicDataSource()")
        public Object datasourceAround(ProceedingJoinPoint point) throws Throwable {
            MethodSignature signature = (MethodSignature)point.getSignature();
            Method method = signature.getMethod();
            DS ds = method.getAnnotation(DS.class);
            if (Objects.nonNull(ds)){
                DataSourceContextHolder.setDataSource(ds.value());
            }
            try {
                return point.proceed();
            } finally {
                DataSourceContextHolder.removeDataSource();
            }
        }
    }
    

    代码使用了@Around,通过ProceedingJoinPoint获取注解信息,拿到注解传递值,然后设置当前线程的数据源

    6、4 测试
    @GetMapping("/getMasterData.do")
    public String getMasterData(){
        TestUser testUser = testUserMapper.selectOne(null);
        return testUser.getUserName();
    }
    @GetMapping("/getSlaveData.do")
    @DS("slave")
    public String getSlaveData(){
        TestUser testUser = testUserMapper.selectOne(null);
        return testUser.getUserName();
    }
    

    注意也可以同时操作多个数据源,只需要在相应方法上添加 @DS注解就可以,(service)
    由于@DS中设置的默认值是:master,因此在调用主数据源时,可以不用进行添加。

    通过执行结果,我们通过@DS也进行了数据源的切换,实现了Mybatis动态切换数据源中的通过注解切换数据源的方式。

    第二种方式使用第三方框架,dynamic-datasource多数源组件
    <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
                <version>3.6.1</version>
            </dependency>
    
    配置数据源
    spring:
      application:
        name: DynamicSourceData
      datasource:
        dynamic:
          #??????????????,?????master
          primary: master
          #???????,??false. true?????????????,false???????
          strict: false
          datasource:
            master:
              url: jdbc:mysql://192.168.64.140:3306/datasource1?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF8&useSSL=false
              username: root
              password: 123456
              driver-class-name: com.mysql.cj.jdbc.Driver
            slave_1:
              url: jdbc:mysql://192.168.64.136:3306/datasource2?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF8&useSSL=false
              username: root
              password: 123456
              driver-class-name: com.mysql.cj.jdbc.Driver
      cache:
        type: redis
      data:
        redis:
          host: localhost
          port: 6379
          #password: 123456
          database: 0 #????0????
          jedis:
            #Redis?????
            pool:
              max-active: 8 #?????
              max-wait: 1ms #???????????
              max-idle: 4 #???????????
              min-idle: 0 #???????????
    mybatis:
      mapper-locations: mappers/**/*.xml
      configuration:
        map-underscore-to-camel-case: true
        log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    
    测试使用
    @RestController
    @RequestMapping("/friend")
    public class FriendController {
        @Autowired
        private FriendService friendService;
        //  主库查询
        @PostMapping("/find")
        public List<Friend> getListDatas() {
            return friendService.findListDatas();
        }
        /**
         * 注意:@DS("dsName") dsName可以为组名也可以为具体某个库的名称
         * 没有@DS  默认数据源
         * 3.使用 @DS 切换数据源。
         * @DS 可以注解在方法上或类上,同时存在就近原则 方法上注解 优先于 类上注解
         * @return
         */
        //   操作从库
        @DS("slave_1")
        @PostMapping("/find1")
        public List<Friend> getListDatas1() {
            return friendService.findListDatas();
        }
        // 主从库同时操作
        @PostMapping("/find2")
        @DSTransactional
        public List<Friend> getListDatas2() {
    //        操作主库数据
            List<Friend> masters = friendService.findMastDatas();
    //        操作从库数据
            List<Friend> salves = friendService.findSlaveDatas();
    
            masters.addAll(salves);
    
            System.out.println(masters);
            return masters;
        }
    }
    
  • 相关阅读:
    Android Activity 重载 onConfigurationCangerd之屏幕方向改变
    Why indigenous forest guardianship is crucial to climate action?
    番外篇:Linux中好玩的指令(Ubuntu环境)
    Windows配置Redis远程访问
    面试心经
    2023年CSP-S赛后总结(2023CSP-S题解)
    测试用例的优化与整理:确保软件质量的关键步骤
    Semtech GS2971AIBE3 3G SDI 接收器
    【会员管理系统】篇一之项目预热
    SLAM第11讲
  • 原文地址:https://blog.csdn.net/qq_37823919/article/details/139396091