SpringBoot實(shí)現(xiàn)動(dòng)態(tài)數(shù)據(jù)源切換的方法總結(jié)
簡(jiǎn)介
項(xiàng)目開(kāi)發(fā)中經(jīng)常會(huì)遇到多數(shù)據(jù)源同時(shí)使用的場(chǎng)景,比如冷熱數(shù)據(jù)的查詢等情況,我們可以使用類(lèi)似現(xiàn)成的工具包來(lái)解決問(wèn)題,但在多數(shù)據(jù)源的使用中通常伴隨著定制化的業(yè)務(wù),所以一般的公司還是會(huì)自行實(shí)現(xiàn)多數(shù)據(jù)源切換的功能,接下來(lái)一起使用實(shí)現(xiàn)自定義注解的形式來(lái)實(shí)現(xiàn)一下。
基礎(chǔ)配置
yml配置
pom.xml
文件引入必要的Jar
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.6</version> </parent> <groupId>com.dynamic</groupId> <artifactId>springboot-dynamic-datasource</artifactId> <version>0.0.1-SNAPSHOT</version> <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> <mybatis.plus.version>3.5.3.1</mybatis.plus.version> <mysql.connector.version>8.0.32</mysql.connector.version> <druid.version>1.2.6</druid.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <!-- springboot核心包 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- mysql驅(qū)動(dòng)包 --> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <version>${mysql.connector.version}</version> </dependency> <!-- lombok工具包 --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <!-- MyBatis Plus --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>${mybatis.plus.version}</version> </dependency> <!-- druid --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>${druid.version}</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.7</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
管理數(shù)據(jù)源
我們應(yīng)用ThreadLocal來(lái)管理數(shù)據(jù)源信息,通過(guò)其中內(nèi)容的get,set,remove方法來(lái)獲取、設(shè)置、刪除當(dāng)前線程對(duì)應(yīng)的數(shù)據(jù)源。
/** * ThreadLocal存放數(shù)據(jù)源變量 * * @author 公眾號(hào):程序員小富 * @date 2023/11/27 11:02 */ public class DataSourceContextHolder { private static final ThreadLocal<String> DATASOURCE_HOLDER = new ThreadLocal<>(); /** * 獲取當(dāng)前線程的數(shù)據(jù)源 * * @return 數(shù)據(jù)源名稱(chēng) */ public static String getDataSource() { return DATASOURCE_HOLDER.get(); } /** * 設(shè)置數(shù)據(jù)源 * * @param dataSourceName 數(shù)據(jù)源名稱(chēng) */ public static void setDataSource(String dataSourceName) { DATASOURCE_HOLDER.set(dataSourceName); } /** * 刪除當(dāng)前數(shù)據(jù)源 */ public static void removeDataSource() { DATASOURCE_HOLDER.remove(); } }
重置數(shù)據(jù)源
創(chuàng)建 DynamicDataSource 類(lèi)并繼承 AbstractRoutingDataSource,這樣我們就可以重置當(dāng)前的數(shù)據(jù)庫(kù)路由,實(shí)現(xiàn)切換成想要執(zhí)行的目標(biāo)數(shù)據(jù)庫(kù)。
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; import javax.sql.DataSource; import java.util.Map; /** * 重置當(dāng)前的數(shù)據(jù)庫(kù)路由,實(shí)現(xiàn)切換成想要執(zhí)行的目標(biāo)數(shù)據(jù)庫(kù) * * @author 公眾號(hào):程序員小富 * @date 2023/11/27 11:02 */ public class DynamicDataSource extends AbstractRoutingDataSource { public DynamicDataSource(DataSource defaultDataSource, Map<Object, Object> targetDataSources) { super.setDefaultTargetDataSource(defaultDataSource); super.setTargetDataSources(targetDataSources); } /** * 這一步是關(guān)鍵,獲取注冊(cè)的數(shù)據(jù)源信息 * @return */ @Override protected Object determineCurrentLookupKey() { return DataSourceContextHolder.getDataSource(); } }
配置數(shù)據(jù)庫(kù)
在 application.yml 中配置數(shù)據(jù)庫(kù)信息,使用dynamic_datasource_1
、dynamic_datasource_2
兩個(gè)數(shù)據(jù)庫(kù)
spring: datasource: type: com.alibaba.druid.pool.DruidDataSource druid: master: url: jdbc:mysql://127.0.0.1:3306/dynamic_datasource_1?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true username: root password: 12345 driver-class-name: com.mysql.cj.jdbc.Driver slave: url: jdbc:mysql://127.0.0.1:3306/dynamic_datasource_2?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true username: root password: 12345 driver-class-name: com.mysql.cj.jdbc.Driver
再將多個(gè)數(shù)據(jù)源注冊(cè)到DataSource
.
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import javax.sql.DataSource; import java.util.HashMap; import java.util.Map; /** * 注冊(cè)多個(gè)數(shù)據(jù)源 * * @author 公眾號(hào):程序員小富 * @date 2023/11/27 11:02 */ @Configuration public class DateSourceConfig { @Bean @ConfigurationProperties("spring.datasource.druid.master") public DataSource dynamicDatasourceMaster() { return DruidDataSourceBuilder.create().build(); } @Bean @ConfigurationProperties("spring.datasource.druid.slave") public DataSource dynamicDatasourceSlave() { return DruidDataSourceBuilder.create().build(); } @Bean(name = "dynamicDataSource") @Primary public DynamicDataSource createDynamicDataSource() { Map<Object, Object> dataSourceMap = new HashMap<>(); // 設(shè)置默認(rèn)的數(shù)據(jù)源為Master DataSource defaultDataSource = dynamicDatasourceMaster(); dataSourceMap.put("master", defaultDataSource); dataSourceMap.put("slave", dynamicDatasourceSlave()); return new DynamicDataSource(defaultDataSource, dataSourceMap); } }
啟動(dòng)類(lèi)配置
在啟動(dòng)類(lèi)的@SpringBootApplication
注解中排除DataSourceAutoConfiguration
,否則會(huì)報(bào)錯(cuò)。
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
到這多數(shù)據(jù)源的基礎(chǔ)配置就結(jié)束了,接下來(lái)測(cè)試一下
測(cè)試切換
準(zhǔn)備SQL
創(chuàng)建兩個(gè)庫(kù)dynamic_datasource_1、dynamic_datasource_2,庫(kù)中均創(chuàng)建同一張表 t_dynamic_datasource_data。
CREATE TABLE `t_dynamic_datasource_data` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `source_name` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`) );
dynamic_datasource_1.t_dynamic_datasource_data表中插入
insert into t_dynamic_datasource_data (source_name) value ('dynamic_datasource_master');
dynamic_datasource_2.t_dynamic_datasource_data表中插入
insert into t_dynamic_datasource_data (source_name) value ('dynamic_datasource_slave');
手動(dòng)切換數(shù)據(jù)源
這里我準(zhǔn)備了一個(gè)接口來(lái)驗(yàn)證,傳入的 datasourceName 參數(shù)值就是剛剛注冊(cè)的數(shù)據(jù)源的key。
/** * 動(dòng)態(tài)數(shù)據(jù)源切換 * * @author 公眾號(hào):程序員小富 * @date 2023/11/27 11:02 */ @RestController public class DynamicSwitchController { @Resource private DynamicDatasourceDataMapper dynamicDatasourceDataMapper; @GetMapping("/switchDataSource/{datasourceName}") public String switchDataSource(@PathVariable("datasourceName") String datasourceName) { DataSourceContextHolder.setDataSource(datasourceName); DynamicDatasourceData dynamicDatasourceData = dynamicDatasourceDataMapper.selectOne(null); DataSourceContextHolder.removeDataSource(); return dynamicDatasourceData.getSourceName(); } }
傳入?yún)?shù)master時(shí):127.0.0.1:9004/switchDataSource/master
傳入?yún)?shù)slave時(shí):127.0.0.1:9004/switchDataSource/slave
通過(guò)執(zhí)行結(jié)果,我們看到傳遞不同的數(shù)據(jù)源名稱(chēng),已經(jīng)實(shí)現(xiàn)了查詢對(duì)應(yīng)的數(shù)據(jù)庫(kù)數(shù)據(jù)。
注解切換數(shù)據(jù)源
上邊已經(jīng)成功實(shí)現(xiàn)了手動(dòng)切換數(shù)據(jù)源,但這種方式頂多算是半自動(dòng),下邊我們來(lái)使用注解方式實(shí)現(xiàn)動(dòng)態(tài)切換。
定義注解
我們先定一個(gè)名為DS
的注解,作用域?yàn)镸ETHOD方法上,由于@DS中設(shè)置的默認(rèn)值是:master,因此在調(diào)用主數(shù)據(jù)源時(shí),可以不用進(jìn)行傳值。
/** * 定于數(shù)據(jù)源切換注解 * * @author 公眾號(hào):程序員小富 * @date 2023/11/27 11:02 */ @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited public @interface DS { // 默認(rèn)數(shù)據(jù)源master String value() default "master"; }
實(shí)現(xiàn)AOP
定義了@DS
注解后,緊接著實(shí)現(xiàn)注解的AOP邏輯,拿到注解傳遞值,然后設(shè)置當(dāng)前線程的數(shù)據(jù)源
import com.dynamic.config.DataSourceContextHolder; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.stereotype.Component; import java.lang.reflect.Method; import java.util.Objects; /** * 實(shí)現(xiàn)@DS注解的AOP切面 * * @author 公眾號(hào):程序員小富 * @date 2023/11/27 11:02 */ @Aspect @Component @Slf4j public class DSAspect { @Pointcut("@annotation(com.dynamic.aspect.DS)") public void dynamicDataSource() { } @Around("dynamicDataSource()") public Object datasourceAround(ProceedingJoinPoint point) throws Throwable { MethodSignature signature = (MethodSignature) point.getSignature(); Method method = signature.getMethod(); DS ds = method.getAnnotation(DS.class); if (Objects.nonNull(ds)) { DataSourceContextHolder.setDataSource(ds.value()); } try { return point.proceed(); } finally { DataSourceContextHolder.removeDataSource(); } } }
測(cè)試注解
再添加兩個(gè)接口測(cè)試,使用@DS
注解標(biāo)注,使用不同的數(shù)據(jù)源名稱(chēng),內(nèi)部執(zhí)行相同的查詢條件,看看結(jié)果如何?
@DS(value = "master") @GetMapping("/dbMaster") public String dbMaster() { DynamicDatasourceData dynamicDatasourceData = dynamicDatasourceDataMapper.selectOne(null); return dynamicDatasourceData.getSourceName(); }
@DS(value = "slave") @GetMapping("/dbSlave") public String dbSlave() { DynamicDatasourceData dynamicDatasourceData = dynamicDatasourceDataMapper.selectOne(null); return dynamicDatasourceData.getSourceName(); }
通過(guò)執(zhí)行結(jié)果,看到通過(guò)應(yīng)用@DS
注解也成功的進(jìn)行了數(shù)據(jù)源的切換。
事務(wù)管理
在動(dòng)態(tài)切換數(shù)據(jù)源的時(shí)候有一個(gè)問(wèn)題是要考慮的,那就是事務(wù)管理是否還會(huì)生效呢?
我們做個(gè)測(cè)試,新增一個(gè)接口分別插入兩條記錄,其中在插入第二條數(shù)據(jù)時(shí)將值設(shè)置超過(guò)了字段長(zhǎng)度限制,會(huì)產(chǎn)生Data too long for column
異常。
/** * 驗(yàn)證一下事物控制 */ // @Transactional(rollbackFor = Exception.class) @DS(value = "slave") @GetMapping("/dbTestTransactional") public void dbTestTransactional() { DynamicDatasourceData datasourceData = new DynamicDatasourceData(); datasourceData.setSourceName("test"); dynamicDatasourceDataMapper.insert(datasourceData); DynamicDatasourceData datasourceData1 = new DynamicDatasourceData(); datasourceData1.setSourceName("testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttest"); dynamicDatasourceDataMapper.insert(datasourceData1); }
經(jīng)過(guò)測(cè)試發(fā)現(xiàn)執(zhí)行結(jié)果如下,即便實(shí)現(xiàn)動(dòng)態(tài)切換數(shù)據(jù)源,本地事務(wù)依然可以生效。
- 不加上
@Transactional
注解第一條記錄可以插入,第二條插入失敗 - 加上
@Transactional
注解兩條記錄都不會(huì)插入成功
本文案例地址:
以上就是SpringBoot實(shí)現(xiàn)動(dòng)態(tài)數(shù)據(jù)源切換的方法總結(jié)的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot動(dòng)態(tài)數(shù)據(jù)源切換的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- SpringBoot動(dòng)態(tài)數(shù)據(jù)源連接測(cè)試的操作詳解
- springboot配置多數(shù)據(jù)源(靜態(tài)和動(dòng)態(tài)數(shù)據(jù)源)
- SpringBoot中動(dòng)態(tài)數(shù)據(jù)源是實(shí)現(xiàn)與用途
- Springboot實(shí)現(xiàn)根據(jù)用戶ID切換動(dòng)態(tài)數(shù)據(jù)源
- 如何在Java SpringBoot項(xiàng)目中配置動(dòng)態(tài)數(shù)據(jù)源你知道嗎
- 詳解SpringBoot+Mybatis實(shí)現(xiàn)動(dòng)態(tài)數(shù)據(jù)源切換
- SpringBoot Mybatis動(dòng)態(tài)數(shù)據(jù)源切換方案實(shí)現(xiàn)過(guò)程
- 通過(guò)springboot+mybatis+druid配置動(dòng)態(tài)數(shù)據(jù)源
- SpringBoot整合MyBatisPlus配置動(dòng)態(tài)數(shù)據(jù)源的方法
- springboot 動(dòng)態(tài)數(shù)據(jù)源的實(shí)現(xiàn)方法(Mybatis+Druid)
- SpringBoot實(shí)現(xiàn)動(dòng)態(tài)數(shù)據(jù)源切換的項(xiàng)目實(shí)踐
相關(guān)文章
jmeter接口測(cè)試教程及接口測(cè)試流程詳解(全網(wǎng)僅有)
Jmeter是由Apache公司開(kāi)發(fā)的一個(gè)純Java的開(kāi)源項(xiàng)目,即可以用于做接口測(cè)試也可以用于做性能測(cè)試。本文給大家分享jmeter接口測(cè)試教程及接口測(cè)試流程,感興趣的朋友跟隨小編一起看看吧2021-12-12SpringBoot項(xiàng)目配置文件注釋亂碼的問(wèn)題解決方案
這篇文章主要介紹了SpringBoot 項(xiàng)目配置文件注釋亂碼的問(wèn)題解決方案,文中通過(guò)圖文結(jié)合的方式給大家講解的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下2024-07-07Ribbon負(fù)載均衡服務(wù)調(diào)用的示例詳解
Rbbo其實(shí)就是一個(gè)軟負(fù)載均衡的客戶端組件,他可以和其他所需請(qǐng)求的客戶端結(jié)合使用,這篇文章主要介紹了Ribbon負(fù)載均衡服務(wù)調(diào)用案例代碼,需要的朋友可以參考下2023-01-01java POI 如何實(shí)現(xiàn)Excel單元格內(nèi)容換行
這篇文章主要介紹了java POI 如何實(shí)現(xiàn)Excel單元格內(nèi)容換行的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07Spring Data JPA帶條件分頁(yè)查詢實(shí)現(xiàn)原理
這篇文章主要介紹了Spring Data JPA帶條件分頁(yè)查詢實(shí)現(xiàn)原理,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-05-05Maven多模塊工程Module開(kāi)發(fā)(圖文教程)
這篇文章主要介紹了Maven多模塊工程Module開(kāi)發(fā)(圖文教程),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-12-12Maven中的dependencyManagement 實(shí)例詳解
dependencyManagement的中文意思就是依賴(lài)關(guān)系管理,它就是為了能通更好統(tǒng)一管理項(xiàng)目的版本號(hào)和各種jar版本號(hào),可以更加方便升級(jí),解決包沖突問(wèn)題,這篇文章主要介紹了Maven中的dependencyManagement 實(shí)例詳解,需要的朋友可以參考下2024-02-02Java開(kāi)發(fā)中常用的 Websocket 技術(shù)參考
WebSocket 使得客戶端和服務(wù)器之間的數(shù)據(jù)交換變得更加簡(jiǎn)單,允許服務(wù)端主動(dòng)向客戶端推送數(shù)據(jù),當(dāng)然也支持客戶端發(fā)送數(shù)據(jù)到服務(wù)端。2020-09-09