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

🎯 总结

主从同步架构的优势:

优势 描述
提升读性能 将大量读请求分发到从库,减轻主库压力
提高可用性 从库可用作备份库,主库故障时从库可切换为主库
数据一致性保障 通过半同步或异步复制保障数据一致性
相关推荐
敖云岚17 分钟前
【Redis】分布式锁的介绍与演进之路
数据库·redis·分布式
LUCIAZZZ1 小时前
HikariCP数据库连接池原理解析
java·jvm·数据库·spring·springboot·线程池·连接池
考虑考虑1 小时前
Springboot3.5.x结构化日志新属性
spring boot·后端·spring
我在北京coding1 小时前
300道GaussDB(WMS)题目及答案。
数据库·gaussdb
小Tomkk1 小时前
阿里云 RDS mysql 5.7 怎么 添加白名单 并链接数据库
数据库·mysql·阿里云
TTDreamTT1 小时前
SpringBoot十二、SpringBoot系列web篇之过滤器Filte详解
spring boot
明月醉窗台2 小时前
qt使用笔记二:main.cpp详解
数据库·笔记·qt
沉到海底去吧Go3 小时前
【图片自动识别改名】识别图片中的文字并批量改名的工具,根据文字对图片批量改名,基于QT和腾讯OCR识别的实现方案
数据库·qt·ocr·图片识别自动改名·图片区域识别改名·pdf识别改名
老纪的技术唠嗑局3 小时前
重剑无锋,大巧不工 —— OceanBase 中的 Nest Loop Join 使用技巧分享
数据库·sql
未来之窗软件服务3 小时前
JAVASCRIPT 前端数据库-V6--仙盟数据库架构-—-—仙盟创梦IDE
数据库·数据库架构·仙盟创梦ide·东方仙盟·东方仙盟数据库