Spring Boot + MyBatis-Plus 实现 MySQL 主从复制动态数据源切换
一、前言
下面是一个示例代码,展示如何在 Spring Boot 应用中实现 MySQL 主从复制的数据源动态切换。我们将使用 Spring Boot 和 MyBatis-Plus,并且结合 Spring 的动态数据源切换功能。
1. 添加依赖
首先,确保在 pom.xml
文件中添加了必要的依赖:
<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>
2. 配置主从数据源
在 application.yml
或 application.properties
中配置主从数据源:
server:
port: 8082
spring:
datasource:
master:
url: jdbc:mysql://master-db-host:3306/your_database?useSSL=false
username: your_username
password: your_password
driver-class-name: com.mysql.cj.jdbc.Driver
slave:
url: jdbc:mysql://slave-db-host:3306/your_database?useSSL=false
username: your_username
password: your_password
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis-plus:
mapper-locations: classpath*:/mapper/**/*.xml
3. 创建数据源配置类
在 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();
}
}
4. 创建数据源上下文
DatabaseContextHolder
用于管理当前线程的数据源类型:
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. 定义数据源类型
DatabaseType
枚举定义了数据源类型:
public enum DatabaseType {
MASTER,
SLAVE
}
6. 配置数据源切换
使用 AOP 来控制数据源切换,可以定义一个切面来切换数据源:
@Aspect
@Component
@EnableAspectJAutoProxy
public class DataSourceAspect {
@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 = method.getAnnotation(DataSource.class);
if (dataSource != null) {
DatabaseContextHolder.setDatabaseType(dataSource.type());
}
try {
return point.proceed();
} finally {
DatabaseContextHolder.clearDatabaseType();
}
}
}
7. 创建DynamicDataSourceConfig
@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);
}
}
8. 创建DynamicRoutingDataSource
public class DynamicRoutingDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DatabaseContextHolder.getDatabaseType();
}
}
9. 创建注解
注解用于标记需要读操作的方法:
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
DatabaseType type() default DatabaseType.SLAVE;
}
10. 使用注解
@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);
}
}
这个示例展示了如何使用 Spring Boot 和 MyBatis-Plus 实现 MySQL 主从复制的数据源切换。你可以根据实际情况调整配置和代码。