範例講解Spring boot動態切換資料來源

2023-05-26 12:01:08
摘要:本文模擬一下在主庫查詢訂單資訊查詢不到的時候,切換資料來源去歷史庫裡面查詢。

本文分享自華為雲社群《springboot動態切換資料來源》,作者:小陳沒煩惱 。

前言

在公司的系統裡,由於資料量較大,所以設定了多個資料來源,它會根據使用者所在的地區去查詢那一個資料庫,這樣就產生了動態切換資料來源的場景。

今天,就模擬一下在主庫查詢訂單資訊查詢不到的時候,切換資料來源去歷史庫裡面查詢。

實現效果

首先我們設定查詢的資料庫為db1,可以看到通過訂單號沒有查到訂單資訊,然後我們重置資料來源,重新設定為db2,同樣的訂單號就可以查詢到資訊。

資料庫準備

新建兩個資料庫db1和db2,db1作為主庫,db2作為歷史庫

兩個庫中都有一個訂單表biz_order,主庫中沒有資料,歷史庫中有我們要查詢的資料。

程式碼編寫

1.新建一個springboot專案,引入所需依賴

 <dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter</artifactId>
 </dependency>
 <!--引入druid-替換預設資料庫連線池-->
 <dependency>
 <groupId>com.alibaba</groupId>
 <artifactId>druid-spring-boot-starter</artifactId>
 <version>1.2.15</version>
 </dependency>
 <dependency>
 <groupId>org.mybatis.spring.boot</groupId>
 <artifactId>mybatis-spring-boot-starter</artifactId>
 <version>2.2.2</version>
 </dependency>
 <!--mysql驅動-->
 <dependency>
 <groupId>mysql</groupId>
 <artifactId>mysql-connector-java</artifactId>
 <version>8.0.30</version>
 </dependency>
 <dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-test</artifactId>
 <scope>test</scope>
 </dependency>

2.application.yaml設定資料庫資訊

這裡我們設定兩個資料庫的資訊

spring:
 datasource:
    db1:
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://localhost/db1?characterEncoding=utf8&characterSetResults=utf8&autoReconnect=true&failOverReadOnly=false
      username: root
      password: root
      type: com.alibaba.druid.pool.DruidDataSource
    db2:
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://localhost/db2?characterEncoding=utf8&characterSetResults=utf8&autoReconnect=true&failOverReadOnly=false
      username: root
      password: root
      type: com.alibaba.druid.pool.DruidDataSource
mybatis:
  mapper-locations: classpath:mapper/*.xml

3.建立資料來源物件,並注入spring容器中

新建DynamicDataSourceConfig.java檔案,在該組態檔中讀取yaml設定的資料來源資訊,並且通過該資訊構造資料來源物件,然後通過@Bean註解注入到spring容器中。

package com.it1997.config;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import javax.sql.DataSource;
@Configuration
public class DynamicDataSourceConfig {
    @Bean("dataSource1")
    @ConfigurationProperties(prefix = "spring.datasource.db1")
 public DataSource oneDruidDataSource() {
 return DruidDataSourceBuilder.create().build();
 }
    @Bean("dataSource2")
    @ConfigurationProperties(prefix = "spring.datasource.db2")
 public DataSource twoDruidDataSource() {
 return DruidDataSourceBuilder.create().build();
 }
    @Bean
 public DataSourceTransactionManager dataSourceTransactionManager1(@Qualifier("dataSource1") DataSource dataSource1) {
 DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
 dataSourceTransactionManager.setDataSource(dataSource1);
 return dataSourceTransactionManager;
 }
    @Bean
 public DataSourceTransactionManager dataSourceTransactionManager2(@Qualifier("dataSource2") DataSource dataSource2) {
 DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
 dataSourceTransactionManager.setDataSource(dataSource2);
 return dataSourceTransactionManager;
 }
}

4.資料來源設定上下文資訊

新建DynamicDataSourceHolder.java檔案,該檔案通過ThreadLocal,實現為每一個執行緒建立一個儲存資料來源設定的上下文。並且提供setDataSource和getDataSource靜態方法來設定和獲取資料來源的名稱。

package com.it1997.config;
public class DynamicDataSourceHolder {
 private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
 public static void setDataSource(String dataSource) {
 contextHolder.set(dataSource);
 }
 public static String getDataSource() {
 return contextHolder.get();
 }
 public static void clearDataSource() {
 contextHolder.remove();
 }
}

5.重寫資料來源設定類

新建DynamicDataSource.java檔案,該類繼承AbstractRoutingDataSource 類,重寫父類別determineCurrentLookupKey和afterPropertiesSet方法。

這裡我們重寫父類別中afterPropertiesSet方法(為什麼要重寫在這個方法,可以看文章最後對於druid的原始碼的講解),在這個方法裡我們將spring容器中的所有的資料來源,都給放到map裡,然後後續我們根據map中的key來獲取不同的資料來源,super.afterPropertiesSet();通過這個方法設定上資料來源。

在類上加上@Primary註解,讓spring容器優先使用我們自定義的資料來源,否則還是會使用預設的資料來源設定。

package com.it1997.config;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
@Component
@Primary
public class DynamicDataSource extends AbstractRoutingDataSource {
    @Resource
 DataSource dataSource1;
    @Resource
 DataSource dataSource2;
    @Override
 protected Object determineCurrentLookupKey() {
 return DynamicDataSourceHolder.getDataSource();
 }
    @Override
 public void afterPropertiesSet() {
 // 初始化所有資料來源
        Map<Object, Object> targetDataSource = new HashMap<>();
 targetDataSource.put("db1", dataSource1);
 targetDataSource.put("db2", dataSource2);
 super.setTargetDataSources(targetDataSource);
 super.setDefaultTargetDataSource(dataSource1);
 super.afterPropertiesSet();
 }
}

druid資料來源設定解讀

點開我們剛剛繼承的AbstractRoutingDataSource抽象類,可以看到它又繼承了AbstractDataSource 實現了InitializingBean介面。

然後我們在看一下druid的資料來源設定是怎麼實現的,點開DruidDataSourceWrapper類,可以看到它也是繼承了AbstractDataSource 實現了InitializingBean介面。並且,讀取的是yaml檔案中spring.datasource.druid下面設定的資料庫連線資訊。

而我們自定的一的資料來源讀取的是spring.datasource.db1下面設定的資料庫連線資訊。

druid的資料來源設定,實現了介面中afterPropertiesSet,在這個方法中設定了資料庫的基本資訊,例如,資料庫連線地址、使用者名稱、密碼以及資料庫連線驅動資訊。

 

點選關注,第一時間瞭解華為雲新鮮技術~