MySQL 主從復(fù)制是一種常見(jiàn)的數(shù)據(jù)庫(kù)架構(gòu),它可以提高數(shù)據(jù)庫(kù)的性能和可用性。動(dòng)態(tài)數(shù)據(jù)源切換則可以根據(jù)業(yè)務(wù)需求,在不同場(chǎng)景下使用不同的數(shù)據(jù)源,比如在讀多寫少的場(chǎng)景下,可以通過(guò)切換到從庫(kù)來(lái)分擔(dān)主庫(kù)的壓力。
在本文中,我們將介紹如何在 Spring Boot 中實(shí)現(xiàn) MySQL 動(dòng)態(tài)數(shù)據(jù)源切換,使用 MyBatis-Plus 進(jìn)行數(shù)據(jù)庫(kù)操作
那么接下來(lái)我們開(kāi)始項(xiàng)目實(shí)現(xiàn),項(xiàng)目結(jié)構(gòu)如下
前備:可以提前導(dǎo)入sql
create table tb_tutorial
(
id bigint auto_increment comment '主鍵ID'
primary key,
title varchar(40) null comment '標(biāo)題',
description varchar(30) null comment '描述',
published tinyint null comment '1 表示發(fā)布 0 表示未發(fā)布'
);
-- 在從庫(kù)進(jìn)行數(shù)據(jù)添加
INSERT INTO user_db.tb_tutorial
(id, title, description, published)
VALUES(1758812356898889, 'savle', 'savle', 1);
1.引入依賴
在項(xiàng)目的的pom.xml
文件中引入Spring Boot和MyBatis-Plus的相關(guān)依賴
<?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>
<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. 配置數(shù)據(jù)源
在application.yml
文件中配置主從數(shù)據(jù)源信息。注意這里我們要搭建主從數(shù)據(jù)庫(kù),本文只是在一個(gè)mysql實(shí)例中創(chuàng)建兩個(gè)庫(kù),里面存在相同表
正確應(yīng)該是兩個(gè)不同的mysql實(shí)例,作者暫時(shí)沒(méi)有環(huán)境
server:
port: 8082
spring:
datasource:
master:
username: root
password: root
url: jdbc:mysql://localhost:3306/webapi?useUnicode=true&characterEncoding=utf8
driver-class-name: com.mysql.cj.jdbc.Driver
slave:
username: root
password: root
url: jdbc:mysql://localhost:3306/user_db?useUnicode=true&characterEncoding=utf8
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis-plus:
mapper-locations: classpath*:/mapper/**/*.xml
3. 創(chuàng)建DatabaseType 枚舉類型
創(chuàng)建DatabaseType 枚舉類型,用于切換數(shù)據(jù)源時(shí),確定連接的是那個(gè)數(shù)據(jù)源
在com.zbbmeta.config
包下創(chuàng)建DatabaseType枚舉類型
// 定義一個(gè)枚舉類型 DatabaseType,表示系統(tǒng)中的數(shù)據(jù)庫(kù)類型
public enum DatabaseType {
MASTER, // 主數(shù)據(jù)庫(kù)類型
SLAVE // 從數(shù)據(jù)庫(kù)類型
}
4. 配置數(shù)據(jù)源上下文
在com.zbbmeta.holder
包下創(chuàng)建一個(gè)DataSourceContextHolder
類用于保存和獲取當(dāng)前線程使用的數(shù)據(jù)源類型
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. 配置動(dòng)態(tài)數(shù)據(jù)源
我們創(chuàng)建了一個(gè) DynamicDataSource
類,繼承 AbstractRoutingDataSource
,用于實(shí)現(xiàn)動(dòng)態(tài)數(shù)據(jù)源的切換。
AbstractRoutingDataSource 是 Spring Framework 提供的一個(gè)抽象數(shù)據(jù)源類,用于實(shí)現(xiàn)動(dòng)態(tài)數(shù)據(jù)源切換。它允許應(yīng)用程序在運(yùn)行時(shí)動(dòng)態(tài)地切換到不同的數(shù)據(jù)源,從而支持多數(shù)據(jù)源的場(chǎng)景,比如數(shù)據(jù)庫(kù)讀寫分離、主從復(fù)制等
AbstractRoutingDataSource
介紹:
-
動(dòng)態(tài)數(shù)據(jù)源切換: AbstractRoutingDataSource 的核心思想是根據(jù)某個(gè)鍵值(lookup key)來(lái)決定使用哪個(gè)具體的數(shù)據(jù)源。這個(gè)鍵值是通過(guò) determineCurrentLookupKey() 方法提供
-
抽象類: AbstractRoutingDataSource 是一個(gè)抽象類,它提供了模板方法 determineCurrentLookupKey(),需要由子類實(shí)現(xiàn)
-
實(shí)現(xiàn) javax.sql.DataSource 接口: AbstractRoutingDataSource 實(shí)現(xiàn)了 javax.sql.DataSource 接口,因此可以像常規(guī)數(shù)據(jù)源一樣被用于與數(shù)據(jù)庫(kù)的交互。
-
在 Spring 配置中使用: 在 Spring 的配置中,我們可以將
AbstractRoutingDataSource
配置為數(shù)據(jù)源 bean,并將真實(shí)的數(shù)據(jù)源作為其目標(biāo)數(shù)據(jù)源。在需要切換數(shù)據(jù)源的時(shí)候,調(diào)用 determineCurrentLookupKey() 方法,它將返回用于切換數(shù)據(jù)源的鍵值。
在com.zbbmeta.config
包下創(chuàng)建DynamicDataSource
類
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
// 從數(shù)據(jù)源上下文獲取數(shù)據(jù)源類型
return DataSourceContextHolder.getDataSourceType();
}
}
DynamicDataSource類中重寫determineCurrentLookupKey()
方法: 在這個(gè)方法中,我們通過(guò)調(diào)用 DataSourceContextHolder.getDataSourceType()
來(lái)獲取當(dāng)前線程持有的數(shù)據(jù)源類型。這個(gè)方法的返回值將被用作數(shù)據(jù)源的 lookup key,從而實(shí)現(xiàn)動(dòng)態(tài)切換。
6. 添加DataSource注解類
在·com.zbbmeta.annotation
包下創(chuàng)建DataSource
注解類,這是一個(gè)自定義注解,用于標(biāo)記在類或方法上,以指定數(shù)據(jù)源的類型。下面是對(duì)這段代碼的注解說(shuō)明
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
// 默認(rèn)是從數(shù)據(jù)庫(kù)
DatabaseType type() default DatabaseType.SLAVE;
}
注解說(shuō)明:
-
@interface DataSource: 這是一個(gè)注解的聲明,用于創(chuàng)建名為 DataSource 的自定義注解。
-
@Target({ElementType.METHOD, ElementType.TYPE}):
@Target
注解表示此注解可以用于類和方法。在這里,DataSource
注解可以標(biāo)注在類和方法上。 -
@Retention(RetentionPolicy.RUNTIME)
: @Retention 注解表示這個(gè)注解的生命周期,即在運(yùn)行時(shí)仍然可用。這是因?yàn)槲覀兿M谶\(yùn)行時(shí)通過(guò)反射獲取注解信息。 -
DatabaseType type() default DatabaseType.SLAVE
: 這是 DataSource 注解的一個(gè)成員變量。它是一個(gè)枚舉類型的變量,表示數(shù)據(jù)庫(kù)類型,默認(rèn)值為 SLAVE。通過(guò)這個(gè)成員變量,我們可以在使用 DataSource 注解時(shí)指定使用的數(shù)據(jù)源類型
7. 配置數(shù)據(jù)源切換切面
在com.zbbmeta.aspect
報(bào)下創(chuàng)建一個(gè)切面類DataSourceAspect
,用于在執(zhí)行數(shù)據(jù)庫(kù)操作前動(dòng)態(tài)切換數(shù)據(jù)源。
@Aspect
@Component
@EnableAspectJAutoProxy
public class DataSourceAspect {
// 定義切點(diǎn),匹配使用了 @DataSource 注解的方法
@Pointcut("@annotation(com.zbbmeta.annotation.DataSource)")
public void dataSourcePointCut() {}
// 環(huán)繞通知,在方法執(zhí)行前后切換數(shù)據(jù)源
@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) {
// 切換數(shù)據(jù)源類型
DatabaseContextHolder.setDatabaseType(dataSource.type());
}
try {
// 執(zhí)行目標(biāo)方法
return point.proceed();
} finally {
// 清除數(shù)據(jù)源類型,確保線程安全
DatabaseContextHolder.clearDatabaseType();
}
}
}
8. 創(chuàng)建DataSourceConfig
在com.zbbmeta.config
包下創(chuàng)建DataSourceConfig
,用于配置主從兩個(gè)數(shù)據(jù)源
@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 創(chuàng)建DataSourceConfig
在com.zbbmeta.config
包下創(chuàng)建DynamicDataSourceConfig類中配置MyBatis-Plus的相關(guān)內(nèi)容。
@Configuration
@MapperScan("com.zbbmeta.mapper")
public class DynamicDataSourceConfig {
@Autowired
private DataSource masterDataSource;
@Autowired
private DataSource slaveDataSource;
// 配置動(dòng)態(tài)數(shù)據(jù)源
@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); // 設(shè)置默認(rèn)數(shù)據(jù)源
return dynamicDataSource;
}
// 配置 MyBatis 的 SqlSessionFactory
@Bean
public SqlSessionFactory sqlSessionFactory(DataSource dynamicDataSource) throws Exception {
MybatisSqlSessionFactoryBean sessionFactoryBean = new MybatisSqlSessionFactoryBean();
sessionFactoryBean.setDataSource(dynamicDataSource);
// 設(shè)置要掃描的 mapper 接口和 XML 文件路徑
sessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml"));
sessionFactoryBean.setTypeAliasesPackage("com.zbbmeta.entity"); // 設(shè)置實(shí)體類包路徑
return sessionFactoryBean.getObject();
}
// 配置 MyBatis 的 SqlSessionTemplate
@Bean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
10. 測(cè)試
使用MybatisX生成代碼,并且創(chuàng)建com.zbbmeta.controller
包下創(chuàng)建TutorialController
類,并且在需要切換數(shù)據(jù)源的方法上使用 @DataSource 注解,切面將根據(jù)該注解的配置在方法執(zhí)行前后進(jìn)行數(shù)據(jù)源切換。
@RestController
public class TutorialController {
@Autowired
private TutorialService tutorialService;
@DataSource
@GetMapping("/list")
public List<Tutorial> list(){
return tutorialService.list();
}
/**
*
* 功能: 在主庫(kù)創(chuàng)建數(shù)據(jù)
* @return {@link Boolean}
* @author luoheng
*/
@DataSource(type = DatabaseType.MASTER)
@GetMapping("/create")
public Boolean create(){
Tutorial tutorial = new Tutorial();
tutorial.setTitle("master");
tutorial.setDescription("master");
return tutorialService.save(tutorial);
}
}
使用測(cè)試工具,或者瀏覽器發(fā)送請(qǐng)求。
這里由于主,從沒(méi)有數(shù)據(jù)同步,直接請(qǐng)求會(huì)沒(méi)有數(shù)據(jù)。我們可以手動(dòng)新增一下測(cè)試數(shù)據(jù)
Get請(qǐng)求:http://localhost:8082/list
返回結(jié)果:[{"id":10,"title":"savle","description":"savle","published":1}]
Get請(qǐng)求:http://localhost:8082/create
返回結(jié)果:true
這樣就可以實(shí)現(xiàn)動(dòng)態(tài)數(shù)據(jù)切換,實(shí)現(xiàn)讀寫分離。但是這個(gè)時(shí)候會(huì)數(shù)據(jù)庫(kù)不一致,這里提供一篇文章,來(lái)實(shí)現(xiàn)主從數(shù)據(jù)庫(kù)同步。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-825456.html
實(shí)現(xiàn)兩個(gè)MySQL數(shù)據(jù)庫(kù)之間數(shù)據(jù)同步的方案_兩個(gè)mysql數(shù)據(jù)庫(kù)實(shí)時(shí)同步-CSDN博客文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-825456.html
到了這里,關(guān)于Spring Boot + MyBatis-Plus 實(shí)現(xiàn) MySQL 主從復(fù)制動(dòng)態(tài)數(shù)據(jù)源切換的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!