基于Mybatis實(shí)現(xiàn)動(dòng)態(tài)數(shù)據(jù)源切換的示例代碼
引言
在當(dāng)今的互聯(lián)網(wǎng)應(yīng)用中,微服務(wù)大行其道,隨著業(yè)務(wù)的發(fā)展和擴(kuò)展,單一的數(shù)據(jù)庫(kù)無(wú)法滿足日益增長(zhǎng)的數(shù)據(jù)需求,一個(gè)業(yè)務(wù)接口可能需要查詢多個(gè)數(shù)據(jù)源的數(shù)據(jù)組裝到一起返回給頁(yè)面進(jìn)行呈現(xiàn),此時(shí)就需要考慮使用動(dòng)態(tài)數(shù)據(jù)源技術(shù)。 本文將基于 JDK17 + Spring Boot 3 和 MyBatis 框架實(shí)現(xiàn)動(dòng)態(tài)切換數(shù)據(jù)源功能。
代碼開(kāi)發(fā)
- pom.xml
<?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 https://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>3.3.3</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.learning</groupId> <artifactId>learning-mybatis</artifactId> <version>0.0.1-SNAPSHOT</version> <name>learning-mybatis</name> <description>learning-mybatis</description> <url/> <licenses> <license/> </licenses> <developers> <developer/> </developers> <scm> <connection/> <developerConnection/> <tag/> <url/> </scm> <properties> <java.version>17</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>3.0.3</version> </dependency> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter-test</artifactId> <version>3.0.3</version> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <excludes> <exclude> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </exclude> </excludes> </configuration> </plugin> </plugins> </build> </project>
- 啟動(dòng)類 Application.java
@MapperScan("com.learning.**.mapper") @SpringBootApplication(scanBasePackages = {"com.learning"}) public class LearningMybatisApplication { public static void main(String[] args) { SpringApplication.run(LearningMybatisApplication.class, args); } }
- Controller
@RestController @RequestMapping("/student") public class StudentController { @Autowired StudentService studentService; @GetMapping("/selectAll") public String selectAll() { List<Student> students = studentService.selectAll(); for (Student student : students) { System.out.println(student); } return "success"; } }
- Service 和 Impl
public interface IStudentService { List<Student> selectAll(); } @Service public class StudentServiceImpl implements IStudentService { @Autowired StudentMapper mapper; @Override public List<Student> selectAll() { List<Student> students = mapper.selectAll(); List<Student> dataSource2All = mapper.selectDataSource2All(); return mapper.selectAll(); } }
- Mapper
public interface StudentMapper { @DynamicDataSource("dataSource1") List<Student> selectAll(); @DynamicDataSource("dataSource2") List<Student> selectDataSource2All(); }
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.learning.mybatis.mapper.StudentMapper"> <resultMap id="BaseResultMap" type="com.learning.mybatis.entities.Student"> <id property="id" column="id" jdbcType="VARCHAR"/> <result property="name" column="name" jdbcType="VARCHAR"/> <result property="age" column="age" jdbcType="INTEGER"/> </resultMap> <sql id="Base_Column_List"> id,name,age </sql> <select id="selectAll" resultMap="BaseResultMap"> select id,name,age from student </select> <select id="selectDataSource2All" resultMap="BaseResultMap"> select id,name from tx </select> </mapper>
- 配置文件
spring.application.name=learning-mybatis # ======================== Mybatis =============== mybatis.mapper-locations=classpath:mapper/*.xml mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
動(dòng)態(tài)數(shù)據(jù)源切換 AOP 實(shí)現(xiàn)
- 首先,我們需要實(shí)現(xiàn)
AbstractRoutingDataSource
接口的determineCurrentLookupKey()方法
public class DynamicDataSourceRouter extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return DynamicDataSourceContextHolder.getDataSourceType(); } }
- 創(chuàng)建一個(gè)數(shù)據(jù)源上下文持有者,用于保存和獲取當(dāng)前線程的數(shù)據(jù)源。
public class DynamicDataSourceContextHolder { private static final ThreadLocal<String> contextHolder = new InheritableThreadLocal<>(); public static void setDataSourceType(String dataSourceType) { contextHolder.set(dataSourceType); } public static String getDataSourceType() { return contextHolder.get(); } public static void clearDataSourceType() { contextHolder.remove(); } }
- 配置你的數(shù)據(jù)源。這里假設(shè)你已經(jīng)有兩個(gè)數(shù)據(jù)源
dataSource1
和dataSource2
@Configuration public class DataSourceConfig { @Bean public DataSource dataSource() { DynamicDataSourceRouter routingDataSource = new DynamicDataSourceRouter(); Map<Object, Object> dataSourceMap = new HashMap<>(); dataSourceMap.put("dataSource1", dataSource1()); // 你的第一個(gè)數(shù)據(jù)源 dataSourceMap.put("dataSource2", dataSource2()); // 你的第二個(gè)數(shù)據(jù)源 routingDataSource.setTargetDataSources(dataSourceMap); routingDataSource.setDefaultTargetDataSource(dataSource1()); // 默認(rèn)數(shù)據(jù)源 return routingDataSource; } private DataSource dataSource1() { HikariDataSource dataSource = new HikariDataSource(); dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver"); dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/db2024?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true"); dataSource.setUsername("root"); dataSource.setPassword("root"); // 設(shè)置其他HikariDataSource的屬性,如連接池大小等 return dataSource; } private DataSource dataSource2() { HikariDataSource dataSource = new HikariDataSource(); dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver"); dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/tx2021?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true"); dataSource.setUsername("root"); dataSource.setPassword("root"); // 設(shè)置其他HikariDataSource的屬性,如連接池大小等 return dataSource; } }
- 定義注解,用于標(biāo)記需要切換數(shù)據(jù)源的方法。
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface DynamicDataSource { String value(); // 數(shù)據(jù)源名稱 }
- 定義切面,使用AOP來(lái)攔截帶有
@DynamicDataSource
注解的方法,并在方法執(zhí)行前后切換數(shù)據(jù)源
@Aspect @Order(-1) // 確保該AOP在事務(wù)AOP之前執(zhí)行 @Component public class DynamicDataSourceAspect { @Before("@annotation(dynamicDataSource)") public void switchDataSource(JoinPoint point, DynamicDataSource dynamicDataSource) { DynamicDataSourceContextHolder.setDataSourceType(dynamicDataSource.value()); } @After("@annotation(dynamicDataSource)") public void restoreDataSource(JoinPoint point, DynamicDataSource dynamicDataSource) { DynamicDataSourceContextHolder.clearDataSourceType(); } }
至此,完活,拿去測(cè)試看效果。
動(dòng)態(tài)切換數(shù)據(jù)源實(shí)現(xiàn)原理分析
核心代碼:AbstractRoutingDataSource
AbstractRoutingDataSource
是 Spring 框架提供的一個(gè)抽象類,它實(shí)現(xiàn)了 DataSource
接口,內(nèi)部維護(hù)了一個(gè)用來(lái)存儲(chǔ)數(shù)據(jù)源和它們對(duì)應(yīng)的 key的Map,這個(gè) Map 是在構(gòu)造函數(shù)或者配置方法(如 setTargetDataSources
)中設(shè)置的。
// org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource @Nullable private Map<Object, Object> targetDataSources; public void setTargetDataSources(Map<Object, Object> targetDataSources) { this.targetDataSources = targetDataSources; }
determineCurrentLookupKey()
方法是 AbstractRoutingDataSource
的核心。它是一個(gè)抽象方法,子類必須實(shí)現(xiàn)它來(lái)提供當(dāng)前的 key。
// org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource @Nullable protected abstract Object determineCurrentLookupKey();
當(dāng) AbstractRoutingDataSource
的 getConnection()
方法被調(diào)用時(shí),它會(huì)調(diào)用 determineCurrentLookupKey()
來(lái)獲取當(dāng)前的數(shù)據(jù)源 key,然后使用這個(gè) key 從 Map 中獲取對(duì)應(yīng)的數(shù)據(jù)源。
// org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource public Connection getConnection() throws SQLException { return this.determineTargetDataSource().getConnection(); } protected DataSource determineTargetDataSource() { Assert.notNull(this.resolvedDataSources, "DataSource router not initialized"); Object lookupKey = this.determineCurrentLookupKey(); DataSource dataSource = (DataSource)this.resolvedDataSources.get(lookupKey); if (dataSource == null && (this.lenientFallback || lookupKey == null)) { dataSource = this.resolvedDefaultDataSource; } if (dataSource == null) { throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]"); } else { return dataSource; } }
一旦 determineTargetDataSource()
方法返回了合適的數(shù)據(jù)源,AbstractRoutingDataSource
就會(huì)使用這個(gè)數(shù)據(jù)源來(lái)獲取數(shù)據(jù)庫(kù)連接。 由于 determineCurrentLookupKey()
方法在每個(gè)數(shù)據(jù)庫(kù)操作之前都會(huì)被調(diào)用,所以只要在適當(dāng)?shù)牡胤叫薷?determineCurrentLookupKey()
的實(shí)現(xiàn),就可以實(shí)現(xiàn)在不同的數(shù)據(jù)庫(kù)操作間切換數(shù)據(jù)源。
總結(jié)
通過(guò)實(shí)現(xiàn) AbstractRoutingDataSource.determineCurrentLookupKey()
方法,并結(jié)合 Spring 框架內(nèi)部的 AbstractRoutingDataSource
邏輯,我們可以實(shí)現(xiàn)在運(yùn)行時(shí)根據(jù)不同的條件動(dòng)態(tài)地選擇和切換數(shù)據(jù)源。這種機(jī)制允許應(yīng)用程序在處理不同的請(qǐng)求或事務(wù)時(shí)使用不同的數(shù)據(jù)庫(kù)連接,從而提供了極大的靈活性和擴(kuò)展性。
以上就是基于Mybatis實(shí)現(xiàn)動(dòng)態(tài)數(shù)據(jù)源切換的示例代碼的詳細(xì)內(nèi)容,更多關(guān)于Mybatis動(dòng)態(tài)數(shù)據(jù)源切換的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- 解決mybatis-plus動(dòng)態(tài)數(shù)據(jù)源切換不生效的問(wèn)題
- mybatis-plus @DS實(shí)現(xiàn)動(dòng)態(tài)切換數(shù)據(jù)源原理
- 基于mybatis?plus實(shí)現(xiàn)數(shù)據(jù)源動(dòng)態(tài)添加、刪除、切換,自定義數(shù)據(jù)源的示例代碼
- springboot中mybatis多數(shù)據(jù)源動(dòng)態(tài)切換實(shí)現(xiàn)
- 詳解SpringBoot+Mybatis實(shí)現(xiàn)動(dòng)態(tài)數(shù)據(jù)源切換
相關(guān)文章
SpringBoot集成JWT實(shí)現(xiàn)Token登錄驗(yàn)證的示例代碼
隨著技術(shù)的發(fā)展,分布式web應(yīng)用的普及,通過(guò)session管理用戶登錄狀態(tài)成本越來(lái)越高,因此慢慢發(fā)展成為token的方式做登錄身份校驗(yàn),本文就來(lái)介紹一下SpringBoot集成JWT實(shí)現(xiàn)Token登錄驗(yàn)證的示例代碼,感興趣的可以了解一下2023-12-12java實(shí)現(xiàn)jdbc查詢結(jié)果集result轉(zhuǎn)換成對(duì)應(yīng)list集合
本文給大家匯總介紹了java實(shí)現(xiàn)jdbc查詢結(jié)果集result轉(zhuǎn)換成對(duì)應(yīng)list集合,十分的簡(jiǎn)單,有相同需求的小伙伴可以參考下。2015-12-12java swing框架實(shí)現(xiàn)貪吃蛇游戲
這篇文章主要為大家詳細(xì)介紹了java swing框架實(shí)現(xiàn)貪吃蛇游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-12-12Java導(dǎo)出Execl疑難點(diǎn)處理的實(shí)現(xiàn)
這篇文章主要介紹了Java導(dǎo)出Execl疑難點(diǎn)處理的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-04-04Intellij idea遠(yuǎn)程debug連接tomcat實(shí)現(xiàn)單步調(diào)試
這篇文章主要介紹了Intellij idea遠(yuǎn)程debug連接tomcat實(shí)現(xiàn)單步調(diào)試,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-05-05java如何不通過(guò)構(gòu)造函數(shù)創(chuàng)建對(duì)象(Unsafe)
這篇文章主要介紹了java如何不通過(guò)構(gòu)造函數(shù)創(chuàng)建對(duì)象(Unsafe)問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-03-03Maven打包跳過(guò)測(cè)試的實(shí)現(xiàn)方法
使用Maven打包的時(shí)候,可能會(huì)因?yàn)閱卧獪y(cè)試打包失敗,這時(shí)候就需要跳過(guò)單元測(cè)試。本文就介紹了Maven打包跳過(guò)測(cè)試的實(shí)現(xiàn)方法,感興趣的可以了解一下2021-06-06Java多線程之CAS算法實(shí)現(xiàn)線程安全
這篇文章主要介紹了java中如何通過(guò)CAS算法實(shí)現(xiàn)線程安全,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,下面小編和大家一起來(lái)學(xué)習(xí)一下吧2019-05-05