來源:www.liaoxuefeng.com

第一步:配置多資料源
Spring Boot 基礎就不介紹了,推薦下這個實戰教程:
https://github.com/javastacks/spring-boot-best-practice
首先,我們在 SpringBoot 中配置兩個資料源,其中第二個資料源是ro-datasource:
spring:
datasource:
jdbc-url: jdbc:mysql://localhost/test
username: rw
password: rw_password
driver-class-name: com.mysql.jdbc.Driver
hikari:
pool-name: HikariCP
auto-commit: false
...
ro-datasource:
jdbc-url: jdbc:mysql://localhost/test
username: ro
password: ro_password
driver-class-name: com.mysql.jdbc.Driver
hikari:
pool-name: HikariCP
auto-commit: false
...
在開發環境下,沒有必要配置主從資料庫,只需要給資料庫設定兩個用戶,一個rw具有讀寫權限,一個ro只有 SELECT 權限,這樣就模擬了生產環境下對主從資料庫的讀寫分離,
在 SpringBoot 的配置代碼中,我們初始化兩個資料源:
@SpringBootApplication
public class MySpringBootApplication {
/**
* Master data source.
*/
@Bean("masterDataSource")
@ConfigurationProperties(prefix = "spring.datasource")
DataSource masterDataSource() {
logger.info("create master datasource...");
return DataSourceBuilder.create().build();
}
/**
* Slave (read only) data source.
*/
@Bean("slaveDataSource")
@ConfigurationProperties(prefix = "spring.ro-datasource")
DataSource slaveDataSource() {
logger.info("create slave datasource...");
return DataSourceBuilder.create().build();
}
...
}
第二步:撰寫 RoutingDataSource
然后,我們用 Spring 內置的 RoutingDataSource,把兩個真實的資料源代理為一個動態資料源:
public class RoutingDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return "masterDataSource";
}
}
對這個RoutingDataSource,需要在 SpringBoot 中配置好并設定為主資料源:
@SpringBootApplication
public class MySpringBootApplication {
@Bean
@Primary
DataSource primaryDataSource(
@Autowired @Qualifier("masterDataSource") DataSource masterDataSource,
@Autowired @Qualifier("slaveDataSource") DataSource slaveDataSource
) {
logger.info("create routing datasource...");
Map<Object, Object> map = new HashMap<>();
map.put("masterDataSource", masterDataSource);
map.put("slaveDataSource", slaveDataSource);
RoutingDataSource routing = new RoutingDataSource();
routing.setTargetDataSources(map);
routing.setDefaultTargetDataSource(masterDataSource);
return routing;
}
...
}
現在,RoutingDataSource 配置好了,但是,路由的選擇是寫死的,即永遠回傳"masterDataSource",
現在問題來了:如何存盤動態選擇的 key 以及在哪設定 key?
在 Servlet 的執行緒模型中,使用 ThreadLocal 存盤 key 最合適,因此,我們撰寫一個 RoutingDataSourceContext,來設定并動態存盤 key:
public class RoutingDataSourceContext implements AutoCloseable {
// holds data source key in thread local:
static final ThreadLocal<String> threadLocalDataSourceKey = new ThreadLocal<>();
public static String getDataSourceRoutingKey() {
String key = threadLocalDataSourceKey.get();
return key == null ? "masterDataSource" : key;
}
public RoutingDataSourceContext(String key) {
threadLocalDataSourceKey.set(key);
}
public void close() {
threadLocalDataSourceKey.remove();
}
}
然后,修改 RoutingDataSource,獲取 key 的代碼如下:
public class RoutingDataSource extends AbstractRoutingDataSource {
protected Object determineCurrentLookupKey() {
return RoutingDataSourceContext.getDataSourceRoutingKey();
}
}
這樣,在某個地方,例如一個 Controller 的方法內部,就可以動態設定 DataSource 的 Key:
@Controller
public class MyController {
@Get("/")
public String index() {
String key = "slaveDataSource";
try (RoutingDataSourceContext ctx = new RoutingDataSourceContext(key)) {
// TODO:
return "html... www.liaoxuefeng.com";
}
}
}
到此為止,我們已經成功實作了資料庫的動態路由訪問,
這個方法是可行的,但是,需要讀從資料庫的地方,就需要加上一大段try (RoutingDataSourceContext ctx = ...) {}代碼,使用起來十分不便,有沒有方法可以簡化呢?
有!
我們仔細想想,Spring 提供的宣告式事務管理,就只需要一個@Transactional()注解,放在某個 Java 方法上,這個方法就自動具有了事務,
我們也可以撰寫一個類似的@RoutingWith("slaveDataSource")注解,放到某個 Controller 的方法上,這個方法內部就自動選擇了對應的資料源,代碼看起來應該像這樣:
@Controller
public class MyController {
@Get("/")
@RoutingWith("slaveDataSource")
public String index() {
return "html... www.liaoxuefeng.com";
}
}
這樣,完全不修改應用程式的邏輯,只在必要的地方加上注解,自動實作動態資料源切換,這個方法是最簡單的,
想要在應用程式中少寫代碼,我們就得多做一點底層作業:必須使用類似 Spring 實作宣告式事務的機制,即用 AOP 實作動態資料源切換,
實作這個功能也非常簡單,撰寫一個RoutingAspect,利用 AspectJ 實作一個Around攔截:
@Aspect
@Component
public class RoutingAspect {
@Around("@annotation(routingWith)")
public Object routingWithDataSource(ProceedingJoinPoint joinPoint, RoutingWith routingWith) throws Throwable {
String key = routingWith.value();
try (RoutingDataSourceContext ctx = new RoutingDataSourceContext(key)) {
return joinPoint.proceed();
}
}
}
注意方法的第二個引數RoutingWith是 Spring 傳入的注解實體,我們根據注解的value()獲取配置的 key,編譯前需要添加一個 Maven 依賴:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
到此為止,我們就實作了用注解動態選擇資料源的功能,最后一步重構是用字串常量替換散落在各處的"masterDataSource"和"slaveDataSource",
使用限制
受 Servlet 執行緒模型的局限,動態資料源不能在一個請求內設定后再修改,也就是@RoutingWith不能嵌套,此外,@RoutingWith和@Transactional混用時,要設定 AOP 的優先級,
本文代碼需要 SpringBoot 支持,JDK 1.8 編譯并打開-parameters編譯引數,
近期熱文推薦:
1.1,000+ 道 Java面試題及答案整理(2021最新版)
2.勁爆!Java 協程要來了,,,
3.玩大了!Log4j 2.x 再爆雷,,,
4.Spring Boot 2.6 正式發布,一大波新特性,,
5.《Java開發手冊(嵩山版)》最新發布,速速下載!
覺得不錯,別忘了隨手點贊+轉發哦!
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/388908.html
標籤:Java
