Spring Boot 3 实现 MySQL 主从数据库之间的数据同步

Spring Boot 3 实现 MySQL 主从数据库之间的数据同步

在实际项目中,为了提高 系统的读性能数据的可用性 ,通常会使用 主从数据库架构 。Spring Boot 提供了对 多数据源 的良好支持,可以轻松配置 主从数据库 的数据同步,实现 读写分离


🎯 方案介绍

我们将通过 Spring Boot 3 来实现以下目标:

  1. 主库(Master) :处理所有的 写操作INSERTUPDATEDELETE)。
  2. 从库(Slave) :处理所有的 读操作SELECT)。

通过 读写分离 的方式,我们可以有效减轻主库的压力,同时提升系统的读性能。


📋 步骤 1:配置 MySQL 主从同步

首先,确保你的 MySQL 主从服务器已经配置好。

如果你还没有配置主从,请参考以下步骤:

  1. 主库 上启用 binlog 日志。
  2. 从库 上配置 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();
    }
}

🎯 总结

主从同步架构的优势:

优势 描述
提升读性能 将大量读请求分发到从库,减轻主库压力
提高可用性 从库可用作备份库,主库故障时从库可切换为主库
数据一致性保障 通过半同步或异步复制保障数据一致性
相关推荐
架构师沉默1 小时前
Java优雅使用Spring Boot+MQTT推送与订阅
java·开发语言·spring boot
tuokuac1 小时前
MyBatis 与 Spring Boot版本匹配问题
java·spring boot·mybatis
waveee1231 小时前
学习嵌入式的第三十四天-数据结构-(2025.7.29)数据库
数据结构·数据库·学习
何传令1 小时前
SQL优化系统解析
数据库·sql·mysql
找不到、了1 小时前
Redis内存使用耗尽情况分析
数据库·redis·缓存
DarkAthena1 小时前
【GaussDB】内存资源告急:深度诊断一起“memory temporarily unavailable“故障
数据库·gaussdb
小云数据库服务专线1 小时前
GaussDB as的用法
数据库·sql·gaussdb
hzk的学习笔记2 小时前
Redis的事务和Lua之间的区别
数据库·redis·缓存
草履虫建模2 小时前
RuoYi-Vue 项目 Docker 容器化部署 + DockerHub 上传全流程
java·前端·javascript·vue.js·spring boot·docker·dockerhub
77qqqiqi3 小时前
mp核心功能
java·数据库·微服务·mybatisplus