基于Mybatis實現(xiàn)動態(tài)數(shù)據(jù)源切換的示例代碼
引言
在當今的互聯(lián)網(wǎng)應用中,微服務大行其道,隨著業(yè)務的發(fā)展和擴展,單一的數(shù)據(jù)庫無法滿足日益增長的數(shù)據(jù)需求,一個業(yè)務接口可能需要查詢多個數(shù)據(jù)源的數(shù)據(jù)組裝到一起返回給頁面進行呈現(xiàn),此時就需要考慮使用動態(tài)數(shù)據(jù)源技術。 本文將基于 JDK17 + Spring Boot 3 和 MyBatis 框架實現(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 實現(xiàn)
- 首先,我們需要實現(xiàn)
AbstractRoutingDataSource接口的determineCurrentLookupKey()方法
public class DynamicDataSourceRouter extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceContextHolder.getDataSourceType();
}
}
- 創(chuàng)建一個數(shù)據(jù)源上下文持有者,用于保存和獲取當前線程的數(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ù)源。這里假設你已經(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()); // 默認數(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");
// 設置其他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");
// 設置其他HikariDataSource的屬性,如連接池大小等
return dataSource;
}
}
- 定義注解,用于標記需要切換數(shù)據(jù)源的方法。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DynamicDataSource {
String value(); // 數(shù)據(jù)源名稱
}
- 定義切面,使用AOP來攔截帶有
@DynamicDataSource注解的方法,并在方法執(zhí)行前后切換數(shù)據(jù)源
@Aspect
@Order(-1) // 確保該AOP在事務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ù)源實現(xiàn)原理分析
核心代碼:AbstractRoutingDataSource AbstractRoutingDataSource 是 Spring 框架提供的一個抽象類,它實現(xiàn)了 DataSource 接口,內(nèi)部維護了一個用來存儲數(shù)據(jù)源和它們對應的 key的Map,這個 Map 是在構造函數(shù)或者配置方法(如 setTargetDataSources)中設置的。
// org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource
@Nullable
private Map<Object, Object> targetDataSources;
public void setTargetDataSources(Map<Object, Object> targetDataSources) {
this.targetDataSources = targetDataSources;
}
determineCurrentLookupKey() 方法是 AbstractRoutingDataSource 的核心。它是一個抽象方法,子類必須實現(xiàn)它來提供當前的 key。
// org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource @Nullable protected abstract Object determineCurrentLookupKey();
當 AbstractRoutingDataSource 的 getConnection() 方法被調(diào)用時,它會調(diào)用 determineCurrentLookupKey() 來獲取當前的數(shù)據(jù)源 key,然后使用這個 key 從 Map 中獲取對應的數(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)用,所以只要在適當?shù)牡胤叫薷?determineCurrentLookupKey() 的實現(xiàn),就可以實現(xiàn)在不同的數(shù)據(jù)庫操作間切換數(shù)據(jù)源。
總結
通過實現(xiàn) AbstractRoutingDataSource.determineCurrentLookupKey() 方法,并結合 Spring 框架內(nèi)部的 AbstractRoutingDataSource 邏輯,我們可以實現(xiàn)在運行時根據(jù)不同的條件動態(tài)地選擇和切換數(shù)據(jù)源。這種機制允許應用程序在處理不同的請求或事務時使用不同的數(shù)據(jù)庫連接,從而提供了極大的靈活性和擴展性。
以上就是基于Mybatis實現(xiàn)動態(tài)數(shù)據(jù)源切換的示例代碼的詳細內(nèi)容,更多關于Mybatis動態(tài)數(shù)據(jù)源切換的資料請關注腳本之家其它相關文章!
相關文章
SpringBoot集成JWT實現(xiàn)Token登錄驗證的示例代碼
隨著技術的發(fā)展,分布式web應用的普及,通過session管理用戶登錄狀態(tài)成本越來越高,因此慢慢發(fā)展成為token的方式做登錄身份校驗,本文就來介紹一下SpringBoot集成JWT實現(xiàn)Token登錄驗證的示例代碼,感興趣的可以了解一下2023-12-12
java實現(xiàn)jdbc查詢結果集result轉換成對應list集合
本文給大家匯總介紹了java實現(xiàn)jdbc查詢結果集result轉換成對應list集合,十分的簡單,有相同需求的小伙伴可以參考下。2015-12-12
Intellij idea遠程debug連接tomcat實現(xiàn)單步調(diào)試
這篇文章主要介紹了Intellij idea遠程debug連接tomcat實現(xiàn)單步調(diào)試,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-05-05
java如何不通過構造函數(shù)創(chuàng)建對象(Unsafe)
這篇文章主要介紹了java如何不通過構造函數(shù)創(chuàng)建對象(Unsafe)問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-03-03

