SpringBoot 多數(shù)據(jù)源切換(二)(依舊超級簡單)

背景:主從架構(gòu)下,數(shù)據(jù)庫的讀寫分離

1. 依賴
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </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>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.3</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.2.16</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.29</version>
        </dependency>
    </dependencies>
2.配置數(shù)據(jù)源
spring:
  datasource:
    druid:
      type: com.alibaba.druid.pool.DruidDataSource
      master:
        url: jdbc:mysql://127.0.0.1:3307/user1?useUnicode=true&characterEncoding=utf8&useSSL=false
        username: root
        password: root
        driver-class-name: com.mysql.cj.jdbc.Driver
      slave1:
        enabled: true
        url: jdbc:mysql://127.0.0.1:3308/user1?useUnicode=true&characterEncoding=utf8&useSSL=false
        username: root
        password: root
        driver-class-name: com.mysql.cj.jdbc.Driver
      otmstariff:
        enabled: false
        url: jdbc:mysql://127.0.0.1:3306/user1?useUnicode=true&characterEncoding=utf8&useSSL=false
        username: root
        password: root
        driver-class-name: com.mysql.cj.jdbc.Driver
3. 注冊數(shù)據(jù)源

1)創(chuàng)建一個數(shù)據(jù)源枚舉類

public enum DataSourceType {
    /**
     * 主庫
     */
    MASTER,

    /**
     * 從庫
     */
    SLAVE1,

    SLAVE2
}

2)我們切換數(shù)據(jù)庫所需要的bean全部交給spring容器中

@Configuration
public class DynamicDataSourceConfig {

    @Bean
    @Qualifier("masterDataSource")
    @ConfigurationProperties("spring.datasource.druid.master")
    public DataSource masterDataSource() {
        return DruidDataSourceBuilder.create().build();
    }

    @Qualifier("slave1DataSource")
    @Bean
    @ConfigurationProperties("spring.datasource.druid.slave1")
    // 根據(jù)配置文件enabled屬性,判斷該配置是否生效
    @ConditionalOnProperty(prefix = "spring.datasource.druid.slave1", name = "enabled", havingValue = "true")
    public DataSource slave1DataSource() {
        return DruidDataSourceBuilder.create().build();
    }

    @Qualifier("slave2DataSource")
    @Bean
    @ConfigurationProperties("spring.datasource.druid.slave2")
    @ConditionalOnProperty(prefix = "spring.datasource.druid.slave2", name = "enabled", havingValue = "true")
    public DataSource slave2DataSource() {
        return DruidDataSourceBuilder.create().build();
    }



    @Bean(name = "dynamicDataSource")
    @Primary
    public DynamicDataSource dataSource(DataSource masterDataSource) {
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put(DataSourceType.MASTER.name(), masterDataSource);
        setDataSource(targetDataSources, DataSourceType.SLAVE1.name(), "slave1DataSource");
        setDataSource(targetDataSources, DataSourceType.SLAVE2.name(), "slave2DataSource");
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        dynamicDataSource.setTargetDataSources(targetDataSources);
        dynamicDataSource.setDefaultTargetDataSource(masterDataSource);
        return dynamicDataSource;
    }

    /**
     * 設(shè)置數(shù)據(jù)源
     *
     * @param targetDataSources 備選數(shù)據(jù)源集合
     * @param sourceName 數(shù)據(jù)源名稱
     * @param beanName bean名稱
     */
    public void setDataSource(Map<Object, Object> targetDataSources, String sourceName, String beanName)
    {
        try
        {
            DataSource dataSource = SpringUtils.getBean(beanName);
            targetDataSources.put(sourceName, dataSource);
        }
        catch (Exception e)
        {
        }
    }


}

4.切換數(shù)據(jù)源
public class DynamicDataSourceContextHolder {
    // 線程安全
    private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
    /**
     * 設(shè)置數(shù)據(jù)源變量
     * @param dataSourceEnum 數(shù)據(jù)源變量
     */
    public static void setDataSourceType(String type) {
        CONTEXT_HOLDER.set(type);
    }
    /**
     * 獲取數(shù)據(jù)源變量
     * @return 數(shù)據(jù)源變量
     */
    public static String getDataSourceType() {
        return CONTEXT_HOLDER.get();
    }
    /**
     * 清理數(shù)據(jù)源
     * @return 數(shù)據(jù)源變量
     */
    public static void clearDataSourceType() {
        CONTEXT_HOLDER.remove();
    }
}

5.設(shè)置數(shù)據(jù)源

新建DynamicDataSource類繼承AbstractRoutingDataSource類,并實現(xiàn)determineCurrentLookupKey方法,該方法是指定當(dāng)前默認(rèn)數(shù)據(jù)源的方法,該類是實現(xiàn)動態(tài)切換數(shù)據(jù)源的關(guān)鍵

public class DynamicDataSource extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
        return DynamicDataSourceContextHolder.getDataSourceType();
    }
}
6. 自定義多數(shù)據(jù)源切換注解
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
    DataSourceType value() default DataSourceType.MASTER;
}

7. AOP攔截器的實現(xiàn)
@Slf4j
@Aspect
@Component
public class DataSourceAspect {

    @Pointcut("@annotation(com.example.datasourceprimordialdemo2.datasource.annotation.DataSource)")
    public void doPointCut() {
    }

    @Around("doPointCut()")
    public Object around(ProceedingJoinPoint pointcut) throws Throwable {
        MethodSignature signature = (MethodSignature) pointcut.getSignature();
        Method method = signature.getMethod();
        DataSource dataSource = method.getAnnotation(DataSource.class);
        if (Objects.nonNull(dataSource)) {
            DynamicDataSourceContextHolder.setDataSourceType(dataSource.value().name());
        }
        try {
            return pointcut.proceed();
        } finally {
            // 銷毀數(shù)據(jù)源 在執(zhí)行方法之后
            DynamicDataSourceContextHolder.clearDataSourceType();
        }
    }
}
8. 啟動類修改
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)

9. 使用

此處為了測試,直接放在controller使用

    @GetMapping("/0")
    @DataSource(DataSourceType.MASTER)
    public ResponseEntity<List<User>> query() {
        return ResponseEntity.ok(this.userService.query());
    }

    @GetMapping("/1")
    @DataSource(DataSourceType.SLAVE1)
    public ResponseEntity<List<User>> query2() {
        return ResponseEntity.ok(this.userService.query());
    }

    @GetMapping("/2")
    @DataSource(DataSourceType.SLAVE2)
    public ResponseEntity<List<User>> query3() {
        return ResponseEntity.ok(this.userService.query());
    }
為了區(qū)分?jǐn)?shù)據(jù)不一樣,數(shù)據(jù)庫未做主從同步

master的數(shù)據(jù)

在這里插入圖片描述

slave1的數(shù)據(jù)

在這里插入圖片描述

slave2的數(shù)據(jù)

在這里插入圖片描述
5.用postman進(jìn)行測試

獲取master的數(shù)據(jù)

[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-N4UsMMk8-1677832353467)(img/img_3.png)]

獲取slave1的數(shù)據(jù)

在這里插入圖片描述

獲取slave2的數(shù)據(jù)

在這里插入圖片描述

完成~~~ 源碼: gitee github

注: 為什么配置了三個數(shù)據(jù)源,是為了展示我在做的過程中遇到的一個問題

  1. 剛開始我是按照網(wǎng)上查到的方式注冊數(shù)據(jù)源
在這里插入圖片描述
  1. 這樣做,在全部數(shù)據(jù)源都注入的時候沒有問題,當(dāng)我在配置中心停掉其中一個數(shù)據(jù)源時就會出現(xiàn)問題
在這里插入圖片描述

3)問題(在masterDataSource添加@Primary又會出現(xiàn)其他的錯誤)

Parameter 1 of method dataSource in com.example.datasourceprimordialdemo2.datasource.config.DynamicDataSourceConfig required a single bean, but 2 were found:
    - masterDataSource: defined by method 'masterDataSource' in class path resource [com/example/datasourceprimordialdemo2/datasource/config/DynamicDataSourceConfig.class]
    - slave2DataSource: defined by method 'slave2DataSource' in class path resource [com/example/datasourceprimordialdemo2/datasource/config/DynamicDataSourceConfig.class]
Action:
Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed
在這里插入圖片描述

4.解決辦法(此方式只針對我代碼的解決辦法,可能會有其他問題導(dǎo)致 此報錯,請再尋找其他方法)

在這里插入圖片描述
  1. 大佬們?nèi)绻衅渌绞秸堅谠u論區(qū)告知,萬分感謝

我是Tz ,想把我遇到的問題都分享給你,想看更多精彩內(nèi)容,請關(guān)注我的wx公眾號zhuangtian

本文由博客一文多發(fā)平臺 OpenWrite 發(fā)布!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容