当前位置: 首页 > article >正文

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 依赖。

<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

配置 主库从库 的数据源。

# 主库数据源
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
@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
@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
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
public class DynamicDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceContextHolder.getDataSource();
    }
}
创建 DataSourceAspect
@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:测试读写分离

创建一个测试服务类
@Service
public class UserService {

    @Transactional
    public void writeUser(User user) {
        userRepository.save(user);
    }

    @Transactional(readOnly = true)
    public List<User> readUsers() {
        return userRepository.findAll();
    }
}
测试读写操作
@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();
    }
}

🎯 总结

主从同步架构的优势:

优势描述
提升读性能将大量读请求分发到从库,减轻主库压力
提高可用性从库可用作备份库,主库故障时从库可切换为主库
数据一致性保障通过半同步或异步复制保障数据一致性

http://www.kler.cn/a/467636.html

相关文章:

  • 封装/前线修饰符/Idea项目结构/package/impore
  • 202-01-06 Unity 使用 Tip1 —— UnityHub 模块卸载重装
  • (已开源-AAAI25) RCTrans:雷达相机融合3D目标检测模型
  • 关于markdown实现页面跳转(调查测试:csdn(博客编写效果、发布效果)、typroa中md转pdf的使用情况)
  • 前端(API)学习笔记(CLASS 4):进阶
  • 如何利用人工智能算法优化知识分类和标签?
  • 手搓人工神经网络
  • Introducing Optimization
  • 基于生成式对抗网络(GAN)的前沿研究与应用
  • 单片机-独立按键矩阵按键实验
  • [Qt] 输入控件 | Line | Text | Combo | Spin | Date | Dial | Slider
  • python基于diagrams库绘制系统架构图
  • 基于Redis有序集合实现滑动窗口限流
  • 【C#特性整理】C#特性及语法基础
  • C# 找出给定三角形的所有角度(Find all angles of a given triangle)
  • 银行系统安全用电解决方案
  • Day29:continue 语句
  • 什么是.net framework,什么是.net core,什么是.net5~8,版本对应关系
  • linux 系统配置ip
  • Linux 内核中网络接口的创建与管理
  • win11 vs2022 opencv 4.10使用vs Image Watch插件实时可视化内存mat对象
  • 洛谷P5318 【深基18.例3】查找文献(c嘎嘎)
  • 常见的框架漏洞
  • 【OceanBase】使用 Superset 连接 OceanBase 数据库并进行数据可视化分析
  • vuedraggable 选项介绍
  • SSM-SpringMVC