Spring Boot + MyBatis-Plus 實現 MySQL 主從復制動態數據源切換
MySQL 主從復制是一種常見的數據庫架構,它可以提高數據庫的性能和可用性。動態數據源切換則可以根據業務需求,在不同場景下使用不同的數據源,比如在讀多寫少的場景下,可以通過切換到從庫來分擔主庫的壓力。
在本文中,我們將介紹如何在 Spring Boot 中實現 MySQL 主從復制和動態數據源切換,使用 MyBatis-Plus 進行數據庫操作
#代碼地址
https://github.com/bangbangzhou/spring-boot-dynamic-master-slave.git
今日內容介紹,大約花費19分鐘
圖片
那么接下來我們開始項目實現,項目結構如下
圖片
1.引入依賴
在項目的的pom.xml文件中引入Spring Boot和MyBatis-Plus的相關依賴
<?xml versinotallow="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>
<artifactId>spring-boot-starter-parent</artifactId>
<groupId>org.springframework.boot</groupId>
<version>2.7.15</version>
</parent>
<groupId>com.zbbmeta</groupId>
<artifactId>spring-boot-dynamic-master-slave</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.30</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.20</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</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>
2. 配置數據源
在application.yml文件中配置主從數據源信息。注意這里我們要搭建主從數據庫,只是在一個mysql實例中創建兩個庫,里面存在相同表
server:
port: 8082
spring:
datasource:
master:
username: root
password: root
url: jdbc:mysql://localhost:3306/shiro_db?useUnicode=true&characterEncoding=utf8
driver-class-name: com.mysql.cj.jdbc.Driver
slave:
username: root
password: root
url: jdbc:mysql://localhost:3306/backend_db?useUnicode=true&characterEncoding=utf8
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis-plus:
mapper-locations: classpath*:/mapper/**/*.xml
3. 創建DatabaseType 枚舉類型
創建DatabaseType 枚舉類型,用于切換數據源時,確定連接的是哪個數據源
在com.zbbmeta.config包下創建DatabaseType枚舉類型
// 定義一個枚舉類型 DatabaseType,表示系統中的數據庫類型
public enum DatabaseType {
MASTER, // 主數據庫類型
SLAVE // 從數據庫類型
}
4. 配置數據源上下文
在com.zbbmeta.holder包下創建一個DataSourceContextHolder類用于保存和獲取當前線程使用的數據源類型
public class DatabaseContextHolder {
private static final ThreadLocal<DatabaseType> contextHolder = new ThreadLocal<>();
public static void setDatabaseType(DatabaseType databaseType) {
contextHolder.set(databaseType);
}
public static DatabaseType getDatabaseType() {
return contextHolder.get();
}
public static void clearDatabaseType() {
contextHolder.remove();
}
}
5. 配置動態數據源
我們創建了一個 DynamicDataSource 類,繼承 AbstractRoutingDataSource,用于實現動態數據源的切換。
AbstractRoutingDataSource 是 Spring Framework 提供的一個抽象數據源類,用于實現動態數據源切換。它允許應用程序在運行時動態地切換到不同的數據源,從而支持多數據源的場景,比如數據庫讀寫分離、主從復制等
AbstractRoutingDataSource介紹:
- 動態數據源切換:AbstractRoutingDataSource 的核心思想是根據某個鍵值(lookup key)來決定使用哪個具體的數據源。這個鍵值是通過 determineCurrentLookupKey() 方法提供
- 抽象類:AbstractRoutingDataSource 是一個抽象類,它提供了模板方法 determineCurrentLookupKey(),需要由子類實現
- 實現 javax.sql.DataSource 接口:AbstractRoutingDataSource 實現了 javax.sql.DataSource 接口,因此可以像常規數據源一樣被用于與數據庫的交互。
- 在 Spring 配置中使用:在 Spring 的配置中,我們可以將 AbstractRoutingDataSource 配置為數據源 bean,并將真實的數據源作為其目標數據源。在需要切換數據源的時候,調用 determineCurrentLookupKey() 方法,它將返回用于切換數據源的鍵值。
在com.zbbmeta.config包下創建DynamicDataSource類
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDataSourceType();
}
}
DynamicDataSource類中重寫determineCurrentLookupKey()方法:在這個方法中,我們通過調用 DataSourceContextHolder.getDataSourceType() 來獲取當前線程持有的數據源類型。這個方法的返回值將被用作數據源的 lookup key,從而實現動態切換。
6. 添加DataSource注解類
在·com.zbbmeta.annotation包下創建DataSource注解類,這是一個自定義注解,用于標記在類或方法上,以指定數據源的類型。下面是對這段代碼的注解說明
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
DatabaseType type() default DatabaseType.SLAVE;
}
注解說明:
- @interface DataSource:這是一個注解的聲明,用于創建名為 DataSource 的自定義注解。
- @Target({ElementType.METHOD, ElementType.TYPE}):@Target 注解表示此注解可以用于類和方法。在這里,DataSource 注解可以標注在類和方法上。
- @Retention(RetentionPolicy.RUNTIME):@Retention 注解表示這個注解的生命周期,即在運行時仍然可用。這是因為我們希望在運行時通過反射獲取注解信息。
- DatabaseType type() default DatabaseType.SLAVE:這是 DataSource 注解的一個成員變量。它是一個枚舉類型的變量,表示數據庫類型,默認值為 SLAVE。通過這個成員變量,我們可以在使用 DataSource 注解時指定使用的數據源類型
7. 配置數據源切換切面
在com.zbbmeta.aspect包下創建一個切面類DataSourceAspect,用于在執行數據庫操作前動態切換數據源。
@Aspect
@Component
@EnableAspectJAutoProxy
public class DataSourceAspect {
// 定義切點,匹配使用了 @DataSource 注解的方法
@Pointcut("@annotation(com.zbbmeta.annotation.DataSource)")
public void dataSourcePointCut() {}
// 環繞通知,在方法執行前后切換數據源
@Around("dataSourcePointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
// 獲取方法上的 @DataSource 注解
DataSource dataSource = method.getAnnotation(DataSource.class);
if (dataSource != null) {
// 切換數據源類型
DatabaseContextHolder.setDatabaseType(dataSource.type());
}
try {
// 執行目標方法
return point.proceed();
} finally {
// 清除數據源類型,確保線程安全
DatabaseContextHolder.clearDatabaseType();
}
}
}
8. 創建DataSourceConfig
在com.zbbmeta.config包下創建DataSourceConfig,用于配置主從兩個數據源
@Configuration
@Data
public class DataSourceConfig {
@Value("${spring.datasource.master.url}")
private String dbUrl;
@Value("${spring.datasource.master.username}")
private String username;
@Value("${spring.datasource.master.password}")
private String password;
@Value("${spring.datasource.master.driver-class-name}")
private String driverClassName;
@Value("${spring.datasource.slave.url}")
private String slaveDbUrl;
@Value("${spring.datasource.slave.username}")
private String slaveUsername;
@Value("${spring.datasource.slave.password}")
private String slavePassword;
@Value("${spring.datasource.slave.driver-class-name}")
private String slaveDriverClassName;
@Bean
@ConfigurationProperties(prefix = "spring.datasource.master")
public DataSource masterDataSource() {
return DataSourceBuilder.create()
.driverClassName(driverClassName)
.url(dbUrl)
.username(username)
.password(password)
.build();
}
@Bean
@ConfigurationProperties(prefix = "spring.datasource.slave")
public DataSource slaveDataSource() {
return DataSourceBuilder.create()
.driverClassName(slaveDriverClassName)
.url(slaveDbUrl)
.username(slaveUsername)
.password(slavePassword)
.build();
}
}
9 創建DataSourceConfig
在com.zbbmeta.config包下創建DynamicDataSourceConfig類中配置MyBatis-Plus的相關內容。
@Configuration
@MapperScan("com.zbbmeta.mapper")
public class DynamicDataSourceConfig {
@Autowired
private DataSource masterDataSource;
@Autowired
private DataSource slaveDataSource;
// 配置動態數據源
@Bean
@Primary
public DataSource dynamicDataSource() {
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put(DatabaseType.MASTER, masterDataSource);
targetDataSources.put(DatabaseType.SLAVE, slaveDataSource);
DynamicRoutingDataSource dynamicDataSource = new DynamicRoutingDataSource();
dynamicDataSource.setTargetDataSources(targetDataSources);
dynamicDataSource.setDefaultTargetDataSource(masterDataSource); // 設置默認數據源
return dynamicDataSource;
}
// 配置 MyBatis 的 SqlSessionFactory
@Bean
public SqlSessionFactory sqlSessionFactory(DataSource dynamicDataSource) throws Exception {
MybatisSqlSessionFactoryBean sessionFactoryBean = new MybatisSqlSessionFactoryBean();
sessionFactoryBean.setDataSource(dynamicDataSource);
// 設置要掃描的 mapper 接口和 XML 文件路徑
sessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml"));
sessionFactoryBean.setTypeAliasesPackage("com.zbbmeta.entity"); // 設置實體類包路徑
return sessionFactoryBean.getObject();
}
// 配置 MyBatis 的 SqlSessionTemplate
@Bean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
10. 測試
使用MybatisX生成代碼,并且創建com.zbbmeta.controller包下創建TutorialController類,并且在需要切換數據源的方法上使用 @DataSource 注解,切面將根據該注解的配置在方法執行前后進行數據源切換。
圖片
圖片
@RestController
public class TutorialController {
@Autowired
private TutorialService tutorialService;
@DataSource
@GetMapping("/list")
public List<Tutorial> list(){
return tutorialService.list();
}
@DataSource(type = DatabaseType.MASTER)
@GetMapping("/create")
public Boolean create(){
Tutorial tutorial = new Tutorial();
tutorial.setTitle("master");
tutorial.setDescription("master");
return tutorialService.save(tutorial);
}
}
使用POSTMAN發送請求
http://localhost:8082/list
http://localhost:8082/create
#代碼地址
https://github.com/bangbangzhou/spring-boot-dynamic-master-slave.git