在本文中,我们将介绍如何在 Spring Boot 中实现 MySQL 主从复制和动态数据源切换,使用 MyBatis-Plus 进行数据库操作
#代码地址
https://github.com/bangbangzhou/spring-boot-dynamic-master-slave.git
今日内容介绍,大约花费19分钟
图片
那么接下来我们开始项目实现,项目结构如下
图片
1.引入依赖
在项目的的pom.xml文件中引入Spring Boot和MyBatis-Plus的相关依赖
4.0.0
spring-boot-starter-parent
org.springframework.boot
2.7.15
com.zbbmeta
spring-boot-dynamic-master-slave
1.0-SNAPSHOT
11
11
org.springframework.boot
spring-boot-starter-web
mysql
mysql-connector-java
8.0.30
com.baomidou
mybatis-plus-boot-starter
3.5.3
cn.hutool
hutool-all
5.8.20
org.projectlombok
lombok
org.springframework.boot
spring-boot-starter-aop
org.springframework.boot
spring-boot-maven-plugin
org.projectlombok
lombok
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 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
10. 测试
使用MybatisX生成代码,并且创建com.zbbmeta.controller包下创建TutorialController类,并且在需要切换数据源的方法上使用 @DataSource 注解,切面将根据该注解的配置在方法执行前后进行数据源切换。
图片
图片
@RestController
public class TutorialController {
@Autowired
private TutorialService tutorialService;
@DataSource
@GetMapping("/list")
public List 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