基于Mybatis實(shí)現(xiàn)動態(tài)數(shù)據(jù)源切換的示例代碼
引言
在當(dāng)今的互聯(lián)網(wǎng)應(yīng)用中,微服務(wù)大行其道,隨著業(yè)務(wù)的發(fā)展和擴(kuò)展,單一的數(shù)據(jù)庫無法滿足日益增長的數(shù)據(jù)需求,一個業(yè)務(wù)接口可能需要查詢多個數(shù)據(jù)源的數(shù)據(jù)組裝到一起返回給頁面進(jìn)行呈現(xiàn),此時就需要考慮使用動態(tài)數(shù)據(jù)源技術(shù)。 本文將基于 JDK17 + Spring Boot 3 和 MyBatis 框架實(shí)現(xiàn)動態(tài)切換數(shù)據(jù)源功能。
代碼開發(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>
- 啟動類 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
動態(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)建一個數(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)有兩個數(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()); // 你的第一個數(shù)據(jù)源 dataSourceMap.put("dataSource2", dataSource2()); // 你的第二個數(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來攔截帶有
@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(); } }
至此,完活,拿去測試看效果。
動態(tài)切換數(shù)據(jù)源實(shí)現(xiàn)原理分析
核心代碼:AbstractRoutingDataSource
AbstractRoutingDataSource
是 Spring 框架提供的一個抽象類,它實(shí)現(xiàn)了 DataSource
接口,內(nèi)部維護(hù)了一個用來存儲數(shù)據(jù)源和它們對應(yīng)的 key的Map,這個 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
的核心。它是一個抽象方法,子類必須實(shí)現(xiàn)它來提供當(dāng)前的 key。
// org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource @Nullable protected abstract Object determineCurrentLookupKey();
當(dāng) AbstractRoutingDataSource
的 getConnection()
方法被調(diào)用時,它會調(diào)用 determineCurrentLookupKey()
來獲取當(dāng)前的數(shù)據(jù)源 key,然后使用這個 key 從 Map 中獲取對應(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
就會使用這個數(shù)據(jù)源來獲取數(shù)據(jù)庫連接。 由于 determineCurrentLookupKey()
方法在每個數(shù)據(jù)庫操作之前都會被調(diào)用,所以只要在適當(dāng)?shù)牡胤叫薷?determineCurrentLookupKey()
的實(shí)現(xiàn),就可以實(shí)現(xiàn)在不同的數(shù)據(jù)庫操作間切換數(shù)據(jù)源。
總結(jié)
通過實(shí)現(xiàn) AbstractRoutingDataSource.determineCurrentLookupKey()
方法,并結(jié)合 Spring 框架內(nèi)部的 AbstractRoutingDataSource
邏輯,我們可以實(shí)現(xiàn)在運(yùn)行時根據(jù)不同的條件動態(tài)地選擇和切換數(shù)據(jù)源。這種機(jī)制允許應(yīng)用程序在處理不同的請求或事務(wù)時使用不同的數(shù)據(jù)庫連接,從而提供了極大的靈活性和擴(kuò)展性。
以上就是基于Mybatis實(shí)現(xiàn)動態(tài)數(shù)據(jù)源切換的示例代碼的詳細(xì)內(nèi)容,更多關(guān)于Mybatis動態(tài)數(shù)據(jù)源切換的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
SpringBoot集成JWT實(shí)現(xiàn)Token登錄驗(yàn)證的示例代碼
隨著技術(shù)的發(fā)展,分布式web應(yīng)用的普及,通過session管理用戶登錄狀態(tài)成本越來越高,因此慢慢發(fā)展成為token的方式做登錄身份校驗(yàn),本文就來介紹一下SpringBoot集成JWT實(shí)現(xiàn)Token登錄驗(yàn)證的示例代碼,感興趣的可以了解一下2023-12-12java實(shí)現(xiàn)jdbc查詢結(jié)果集result轉(zhuǎn)換成對應(yīng)list集合
本文給大家匯總介紹了java實(shí)現(xiàn)jdbc查詢結(jié)果集result轉(zhuǎn)換成對應(yīng)list集合,十分的簡單,有相同需求的小伙伴可以參考下。2015-12-12java swing框架實(shí)現(xiàn)貪吃蛇游戲
這篇文章主要為大家詳細(xì)介紹了java swing框架實(shí)現(xiàn)貪吃蛇游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2020-12-12Java導(dǎo)出Execl疑難點(diǎn)處理的實(shí)現(xiàn)
這篇文章主要介紹了Java導(dǎo)出Execl疑難點(diǎn)處理的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(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)試,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-05-05java如何不通過構(gòu)造函數(shù)創(chuàng)建對象(Unsafe)
這篇文章主要介紹了java如何不通過構(gòu)造函數(shù)創(chuàng)建對象(Unsafe)問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-03-03Java多線程之CAS算法實(shí)現(xiàn)線程安全
這篇文章主要介紹了java中如何通過CAS算法實(shí)現(xiàn)線程安全,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,下面小編和大家一起來學(xué)習(xí)一下吧2019-05-05