✅ Spring Boot 3 实现 MySQL 主从数据库之间的数据同步
在实际项目中,为了提高 系统的读性能 和 数据的可用性 ,通常会使用 主从数据库架构 。Spring Boot 提供了对 多数据源 的良好支持,可以轻松配置 主从数据库 的数据同步,实现 读写分离。
🎯 方案介绍
我们将通过 Spring Boot 3 来实现以下目标:
- 主库(Master) :处理所有的 写操作 (
INSERT
、UPDATE
、DELETE
)。 - 从库(Slave) :处理所有的 读操作 (
SELECT
)。
通过 读写分离 的方式,我们可以有效减轻主库的压力,同时提升系统的读性能。
📋 步骤 1:配置 MySQL 主从同步
首先,确保你的 MySQL 主从服务器已经配置好。
如果你还没有配置主从,请参考以下步骤:
- 在 主库 上启用 binlog 日志。
- 在 从库 上配置
CHANGE MASTER TO
语句。
具体配置可以参考这里的指南:
👉 MySQL 主从同步配置
📋 步骤 2:Spring Boot 多数据源配置
1️⃣ 添加依赖
在 pom.xml
文件中添加 MySQL 驱动 和 Spring Data JPA 依赖。
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
2️⃣ 配置 application.properties
配置 主库 和 从库 的数据源。
properties
# 主库数据源
spring.datasource.master.url=jdbc:mysql://localhost:3306/master_db
spring.datasource.master.username=root
spring.datasource.master.password=master_password
spring.datasource.master.driver-class-name=com.mysql.cj.jdbc.Driver
# 从库数据源
spring.datasource.slave.url=jdbc:mysql://localhost:3306/slave_db
spring.datasource.slave.username=root
spring.datasource.slave.password=slave_password
spring.datasource.slave.driver-class-name=com.mysql.cj.jdbc.Driver
3️⃣ 配置多数据源
创建 MasterDataSourceConfig
java
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
basePackages = "com.example.repository",
entityManagerFactoryRef = "masterEntityManager",
transactionManagerRef = "masterTransactionManager"
)
public class MasterDataSourceConfig {
@Primary
@Bean(name = "masterDataSource")
@ConfigurationProperties(prefix = "spring.datasource.master")
public DataSource masterDataSource() {
return DataSourceBuilder.create().build();
}
@Primary
@Bean(name = "masterEntityManager")
public LocalContainerEntityManagerFactoryBean masterEntityManager(EntityManagerFactoryBuilder builder,
@Qualifier("masterDataSource") DataSource dataSource) {
return builder
.dataSource(dataSource)
.packages("com.example.entity")
.persistenceUnit("master")
.build();
}
@Primary
@Bean(name = "masterTransactionManager")
public PlatformTransactionManager masterTransactionManager(
@Qualifier("masterEntityManager") EntityManagerFactory entityManagerFactory) {
return new JpaTransactionManager(entityManagerFactory);
}
}
创建 SlaveDataSourceConfig
java
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
basePackages = "com.example.repository",
entityManagerFactoryRef = "slaveEntityManager",
transactionManagerRef = "slaveTransactionManager"
)
public class SlaveDataSourceConfig {
@Bean(name = "slaveDataSource")
@ConfigurationProperties(prefix = "spring.datasource.slave")
public DataSource slaveDataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name = "slaveEntityManager")
public LocalContainerEntityManagerFactoryBean slaveEntityManager(EntityManagerFactoryBuilder builder,
@Qualifier("slaveDataSource") DataSource dataSource) {
return builder
.dataSource(dataSource)
.packages("com.example.entity")
.persistenceUnit("slave")
.build();
}
@Bean(name = "slaveTransactionManager")
public PlatformTransactionManager slaveTransactionManager(
@Qualifier("slaveEntityManager") EntityManagerFactory entityManagerFactory) {
return new JpaTransactionManager(entityManagerFactory);
}
}
4️⃣ 实现读写分离的 AOP 拦截器
为了自动将 写操作 路由到主库、读操作 路由到从库,可以使用 AOP 来实现。
创建 DataSourceContextHolder
java
public class DataSourceContextHolder {
private static final ThreadLocal<String> CONTEXT = new ThreadLocal<>();
public static void setDataSource(String dataSource) {
CONTEXT.set(dataSource);
}
public static String getDataSource() {
return CONTEXT.get();
}
public static void clear() {
CONTEXT.remove();
}
}
创建 DynamicDataSource
java
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDataSource();
}
}
创建 DataSourceAspect
java
@Aspect
@Component
public class DataSourceAspect {
@Pointcut("execution(* com.example.service..*.read*(..))")
public void readPointcut() {}
@Pointcut("execution(* com.example.service..*.write*(..))")
public void writePointcut() {}
@Before("readPointcut()")
public void useSlaveDataSource() {
DataSourceContextHolder.setDataSource("slaveDataSource");
}
@Before("writePointcut()")
public void useMasterDataSource() {
DataSourceContextHolder.setDataSource("masterDataSource");
}
@After("readPointcut() || writePointcut()")
public void clearDataSource() {
DataSourceContextHolder.clear();
}
}
📋 步骤 3:测试读写分离
创建一个测试服务类
java
@Service
public class UserService {
@Transactional
public void writeUser(User user) {
userRepository.save(user);
}
@Transactional(readOnly = true)
public List<User> readUsers() {
return userRepository.findAll();
}
}
测试读写操作
java
@RestController
@RequestMapping("/users")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@PostMapping
public void createUser(@RequestBody User user) {
userService.writeUser(user);
}
@GetMapping
public List<User> getUsers() {
return userService.readUsers();
}
}
🎯 总结
主从同步架构的优势:
优势 | 描述 |
---|---|
提升读性能 | 将大量读请求分发到从库,减轻主库压力 |
提高可用性 | 从库可用作备份库,主库故障时从库可切换为主库 |
数据一致性保障 | 通过半同步或异步复制保障数据一致性 |