Spring Boot 動態(tài)多數(shù)據(jù)源核心思路與關(guān)鍵介紹

01 引言
上一節(jié)我們使用Qoder完成了動態(tài)數(shù)據(jù)源的demo,測試結(jié)果也沒有讓人失望。但是生成的代碼會給我們帶來什么樣的思考,如果是我們自己實現(xiàn),會不會考慮比Agent更加全面呢?
我們帶著閱讀、學習的態(tài)度了解多數(shù)據(jù)源的動態(tài)切換的核心思路。
02 為什么需要動態(tài)多數(shù)據(jù)源
業(yè)務場景驅(qū)動技術(shù)選型:
- 讀寫分離:主庫寫入,從庫讀取,提升并發(fā)能力
- 數(shù)據(jù)隔離:不同業(yè)務模塊使用獨立數(shù)據(jù)庫
- 分庫分表:業(yè)務增長后的必然選擇
- 灰度發(fā)布:新舊數(shù)據(jù)庫并行,逐步遷移流量
核心訴求:在代碼無感知的情況下,根據(jù)業(yè)務需求自動切換數(shù)據(jù)源。
主要的解決思路分大概兩種:
- 繼承
AbstractRoutingDataSource - 使用數(shù)據(jù)庫的
schema
繼承AbstractRoutingDataSource 今天主要學習的技術(shù),簡單說一下數(shù)據(jù)庫的schema。
數(shù)據(jù)庫的schema的前提是一個賬號具有多個數(shù)據(jù)庫的讀寫權(quán)限,通過任意一個數(shù)據(jù)庫連接,來操作所有的數(shù)據(jù)庫。每一個SQL都必須通過tableName.表名的形式。
select * from test.user where id=1; update test.user set name='AI' where id=1
03 實現(xiàn)思路
通過注解的方式,類似Mybaits-Plus的動態(tài)數(shù)據(jù)源dynamic-datasource-spring-boot-starter的注解@DS
3.1 定義數(shù)據(jù)源注解
我們這里直接使用DataSource
/**
* 多數(shù)據(jù)源切換注解
* 用于在方法或類級別指定數(shù)據(jù)源
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
/**
* 數(shù)據(jù)源名稱
* @return 數(shù)據(jù)源 key
*/
String value() default "master";
}關(guān)鍵點:
- 支持方法級和類級,方法級優(yōu)先級更高
- 默認值避免遺忘配置
- 運行時保留供
AOP識別
3.2 ThreadLocal 上下文管理
ThreadLocal是保持同一線程同一數(shù)據(jù)源的關(guān)鍵。
public class DataSourceContextHolder {
private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
public static void setDataSourceKey(String key) {
CONTEXT_HOLDER.set(key);
}
public static String getDataSourceKey() {
return CONTEXT_HOLDER.get();
}
public static void clearDataSourceKey() {
CONTEXT_HOLDER.remove(); // ?? 必須清理!
}
}關(guān)鍵點:
ThreadLocal保證線程隔離,無需加鎖- 每個請求線程獨立維護數(shù)據(jù)源上下文
- 必須清理:防止線程池復用導致的數(shù)據(jù)污染
3.3 動態(tài)路由數(shù)據(jù)源
public class DynamicRoutingDataSource extends AbstractRoutingDataSource {
/**
* 決定當前使用哪個數(shù)據(jù)源
* @return 數(shù)據(jù)源 key
*/
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDataSourceKey();
}
}AbstractRoutingDataSource是spring-jdbc留給我們的擴展點。
Spring 的巧妙設計:
- 每次獲取數(shù)據(jù)庫連接前,自動調(diào)用
determineCurrentLookupKey() - 從
ThreadLocal獲取當前應使用的數(shù)據(jù)源 key - 自動從目標數(shù)據(jù)源 Map 中查找并返回對應數(shù)據(jù)源
3.4AOP自動切換
使用注解自然少不了AOP的切面編程,我們需要通過@DataSource注解獲取運行時數(shù)據(jù)源的信息。
@Aspect
@Component
@Order(1) // ?? 關(guān)鍵:必須小于事務切面的 Order
public class DataSourceAspect {
@Pointcut("@annotation(DataSource) || @within(DataSource)")
public void dataSourcePointcut() {}
@Before("dataSourcePointcut()")
public void before(JoinPoint joinPoint) {
Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
// 優(yōu)先方法注解,其次類注解
DataSource dataSource = method.getAnnotation(DataSource.class);
if (dataSource == null) {
dataSource = joinPoint.getTarget().getClass().getAnnotation(DataSource.class);
}
if (dataSource != null) {
DataSourceContextHolder.setDataSourceKey(dataSource.value());
}
}
@After("dataSourcePointcut()")
public void after(JoinPoint joinPoint) {
DataSourceContextHolder.clearDataSourceKey();
}
}執(zhí)行流程:
- 方法執(zhí)行前 → 解析注解 → 設置數(shù)據(jù)源 key
- 執(zhí)行業(yè)務邏輯 → Spring 路由到對應數(shù)據(jù)源
- 方法執(zhí)行后 → 清理
ThreadLocal→ 防止內(nèi)存泄漏
關(guān)鍵點:
- 優(yōu)先方法注解,其次類注解
- 切換數(shù)據(jù)源的
Order必須小于事務切面的Order,確保切換數(shù)據(jù)源在事務之前執(zhí)行。
04 配置綁定
上面是整個數(shù)據(jù)源動態(tài)切換的整體思路,但是數(shù)據(jù)源怎么配置呢?
4.1 yml 配置
spring:
application:
name: boot-qoder
datasource:
# master 數(shù)據(jù)源配置
master:
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://127.0.0.1:3306/db_master?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: root
# slave1 數(shù)據(jù)源配置
slave1:
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://127.0.0.1:3306/db_slave1?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: root
# slave2 數(shù)據(jù)源配置
slave2:
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://127.0.0.1:3306/db_slave2?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: root4.2 配置類
@Configuration
@MapperScan(basePackages = "com.example.bootqoder.mapper", sqlSessionTemplateRef = "sqlSessionTemplate")
public class DataSourceConfig {
/**
* master 數(shù)據(jù)源
* @return 數(shù)據(jù)源實例
*/
@Bean(name = "masterDataSource")
@ConfigurationProperties(prefix = "spring.datasource.master")
public DataSource masterDataSource() {
return DataSourceBuilder.create()
.type(com.zaxxer.hikari.HikariDataSource.class)
.build();
}
/**
* slave1 數(shù)據(jù)源
* @return 數(shù)據(jù)源實例
*/
@Bean(name = "slave1DataSource")
@ConfigurationProperties(prefix = "spring.datasource.slave1")
public DataSource slave1DataSource() {
return DataSourceBuilder.create()
.type(com.zaxxer.hikari.HikariDataSource.class)
.build();
}
/**
* slave2 數(shù)據(jù)源
* @return 數(shù)據(jù)源實例
*/
@Bean(name = "slave2DataSource")
@ConfigurationProperties(prefix = "spring.datasource.slave2")
public DataSource slave2DataSource() {
return DataSourceBuilder.create()
.type(com.zaxxer.hikari.HikariDataSource.class)
.build();
}
/**
* 創(chuàng)建動態(tài)路由數(shù)據(jù)源
* @return 動態(tài)數(shù)據(jù)源
*/
@Bean(name = "dynamicDataSource")
@Primary
public DataSource dynamicDataSource(
@Qualifier("masterDataSource") DataSource masterDataSource,
@Qualifier("slave1DataSource") DataSource slave1DataSource,
@Qualifier("slave2DataSource") DataSource slave2DataSource) {
DynamicRoutingDataSource routingDataSource = new DynamicRoutingDataSource();
// 配置多個數(shù)據(jù)源
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put("master", masterDataSource);
targetDataSources.put("slave1", slave1DataSource);
targetDataSources.put("slave2", slave2DataSource);
// 設置目標數(shù)據(jù)源
routingDataSource.setTargetDataSources(targetDataSources);
// 設置默認數(shù)據(jù)源
routingDataSource.setDefaultTargetDataSource(masterDataSource);
return routingDataSource;
}
/**
* 創(chuàng)建 SqlSessionFactory
* @param dataSource 數(shù)據(jù)源
* @return SqlSessionFactory
* @throws Exception 異常
*/
@Bean(name = "sqlSessionFactory")
@Primary
public SqlSessionFactory sqlSessionFactory(@Qualifier("dynamicDataSource") DataSource dataSource) throws Exception {
MybatisSqlSessionFactoryBean sessionFactory = new MybatisSqlSessionFactoryBean();
sessionFactory.setDataSource(dataSource);
// 設置 Mapper XML 文件位置
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
sessionFactory.setMapperLocations(resolver.getResources("classpath:mapper/*.xml"));
// 設置 MyBatis Plus 配置
MybatisConfiguration configuration = new MybatisConfiguration();
configuration.setMapUnderscoreToCamelCase(true);
sessionFactory.setConfiguration(configuration);
return sessionFactory.getObject();
}
/**
* 創(chuàng)建 SqlSessionTemplate
* @param sqlSessionFactory SqlSessionFactory
* @return SqlSessionTemplate
*/
@Bean(name = "sqlSessionTemplate")
@Primary
public SqlSessionTemplate sqlSessionTemplate(@Qualifier("sqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
}我們需要單獨每一個數(shù)據(jù)源都要交給Spring管理:
masterDataSourceslave1DataSourceslave2DataSource
然后才能創(chuàng)建動態(tài)數(shù)據(jù)源,統(tǒng)一由dynamicDataSource管理,返回的數(shù)據(jù)源類型為com.example.bootqoder.datasource.DynamicRoutingDataSource,就是3.3創(chuàng)建的動態(tài)路由數(shù)據(jù)源。
最后需要設置sqlSessionFactory,由于使用了Mybaits Plus。就需要將動態(tài)數(shù)據(jù)源設置到com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean里面去。
05 小結(jié)
個人感覺AI生成的代碼,比我預想的要好多了。動態(tài)切換數(shù)據(jù)源,我事先沒有考慮過在事務之后切換數(shù)據(jù)源的異常,但是AI考慮到了。
隨著AI編程的深入,后面可能我們都不太關(guān)注代碼該怎么寫了,從一個執(zhí)行者轉(zhuǎn)變成管理者,手搓代碼可能真的要變成大家調(diào)侃的古法編程了。
到此這篇關(guān)于Spring Boot 動態(tài)多數(shù)據(jù)源核心思路與關(guān)鍵介紹的文章就介紹到這了,更多相關(guān)Spring Boot 動態(tài)多數(shù)據(jù)源內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- springboot配置多數(shù)據(jù)源(靜態(tài)和動態(tài)數(shù)據(jù)源)
- SpringBoot多數(shù)據(jù)源配置并通過注解實現(xiàn)動態(tài)切換數(shù)據(jù)源
- SpringBoot基于AbstractRoutingDataSource實現(xiàn)多數(shù)據(jù)源動態(tài)切換
- springboot中mybatis多數(shù)據(jù)源動態(tài)切換實現(xiàn)
- spring boot多數(shù)據(jù)源動態(tài)切換代碼實例
- Spring Boot + Mybatis多數(shù)據(jù)源和動態(tài)數(shù)據(jù)源配置方法
- Spring Boot 動態(tài)數(shù)據(jù)源示例(多數(shù)據(jù)源自動切換)
相關(guān)文章
SpringBoot監(jiān)控SQL運行情況的流程步驟
Druid是Java語言中最好的數(shù)據(jù)庫連接池,雖然?HikariCP?的速度稍快,但是,Druid能夠提供強大的監(jiān)控和擴展功能?,也是阿里巴巴的開源項目,本文給大家介紹了SpringBoot監(jiān)控SQL運行情況的流程步驟,需要的朋友可以參考下2024-03-03
關(guān)于SpringCloud?Ribbon替換輪詢算法問題
Spring?Cloud?Ribbon是基于Netlix?Ribbon實現(xiàn)的一套客戶端負載均衡的工具。接下來通過本文給大家介紹SpringCloud?Ribbon替換輪詢算法問題,需要的朋友可以參考下2022-01-01
工作中禁止使用Executors快捷創(chuàng)建線程池原理詳解
這篇文章主要為大家介紹了工作中禁止使用Executors快捷創(chuàng)建線程池原理詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-11-11
如何解決報錯:java.net.BindException:無法指定被請求的地址問題
在Linux虛擬機上安裝并啟動Tomcat時遇到啟動失敗的問題,通過檢查端口及配置文件未發(fā)現(xiàn)異常,后發(fā)現(xiàn)/etc/hosts文件中缺少localhost的映射,添加后重啟Tomcat成功,Tomcat啟動時會檢查localhost的IP映射,缺失或錯誤都可能導致啟動失敗2024-10-10
springboot jpa實現(xiàn)優(yōu)雅處理isDelete的默認值
如果多個實體類都有 isDelete 字段,并且你希望在插入時為它們統(tǒng)一設置默認值時改怎么做呢,本文為大家整理了一些方法,希望對大家有所幫助2024-11-11

