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();
    }
}

🎯 总结

主从同步架构的优势:

优势 描述
提升读性能 将大量读请求分发到从库,减轻主库压力
提高可用性 从库可用作备份库,主库故障时从库可切换为主库
数据一致性保障 通过半同步或异步复制保障数据一致性
相关推荐
Leo.yuan7 分钟前
不同数据仓库模型有什么不同?企业如何选择适合的数据仓库模型?
大数据·数据库·数据仓库·信息可视化·spark
xiaogg367822 分钟前
springboot rabbitmq 延时队列消息确认收货订单已完成
spring boot·rabbitmq·java-rabbitmq
麦兜*24 分钟前
MongoDB 6.0 新特性解读:时间序列集合与加密查询
数据库·spring boot·mongodb·spring·spring cloud·系统架构
chat2tomorrow27 分钟前
数据采集平台的起源与演进:从ETL到数据复制
大数据·数据库·数据仓库·mysql·低代码·postgresql·etl
稻草人想看远方29 分钟前
关系型数据库和非关系型数据库
数据库
考虑考虑30 分钟前
Postgerssql格式化时间
数据库·后端·postgresql
依稀i12333 分钟前
Spring Boot + MySQL 创建超级管理员
spring boot·mysql
千里码aicood39 分钟前
【springboot+vue】党员党建活动管理平台(源码+文档+调试+基础修改+答疑)
java·数据库·spring boot
Chan1643 分钟前
【智能协同云图库】基于统一接口架构构建多维度分析功能、结合 ECharts 可视化与权限校验实现用户 / 管理员图库统计、通过 SQL 优化与流式处理提升数据
java·spring boot·后端·sql·spring·intellij-idea·echarts
TDengine (老段)1 小时前
TDengine 选择函数 Max() 用户手册
大数据·数据库·物联网·时序数据库·tdengine·涛思数据