摘要:本文模擬一下在主庫查詢訂單資訊查詢不到的時候,切換資料來源去歷史庫裡面查詢。
本文分享自華為雲社群《springboot動態切換資料來源》,作者:小陳沒煩惱 。
在公司的系統裡,由於資料量較大,所以設定了多個資料來源,它會根據使用者所在的地區去查詢那一個資料庫,這樣就產生了動態切換資料來源的場景。
今天,就模擬一下在主庫查詢訂單資訊查詢不到的時候,切換資料來源去歷史庫裡面查詢。
首先我們設定查詢的資料庫為db1,可以看到通過訂單號沒有查到訂單資訊,然後我們重置資料來源,重新設定為db2,同樣的訂單號就可以查詢到資訊。
新建兩個資料庫db1和db2,db1作為主庫,db2作為歷史庫
兩個庫中都有一個訂單表biz_order,主庫中沒有資料,歷史庫中有我們要查詢的資料。
<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>
這裡我們設定兩個資料庫的資訊
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
新建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; } }
新建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(); } }
新建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(); } }
點開我們剛剛繼承的AbstractRoutingDataSource抽象類,可以看到它又繼承了AbstractDataSource 實現了InitializingBean介面。
然後我們在看一下druid的資料來源設定是怎麼實現的,點開DruidDataSourceWrapper類,可以看到它也是繼承了AbstractDataSource 實現了InitializingBean介面。並且,讀取的是yaml檔案中spring.datasource.druid下面設定的資料庫連線資訊。
而我們自定的一的資料來源讀取的是spring.datasource.db1下面設定的資料庫連線資訊。
druid的資料來源設定,實現了介面中afterPropertiesSet,在這個方法中設定了資料庫的基本資訊,例如,資料庫連線地址、使用者名稱、密碼以及資料庫連線驅動資訊。