️ Spring Boot 3实现MySQL读写分离完整指南

2025-12-04 0 301

读写分离的核心价值

在高并发场景下,数据库往往成为系统瓶颈。读写分离通过将写操作定向到主库、读操作分发到从库,显著提升系统读性能和数据可用性。当主库出现故障时,从库可以继续提供读服务,提高系统的稳定性。

️ 项目依赖配置

首先在pom.xml中添加必要依赖:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.33</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
    <dependency>
        <groupId>com.zaxxer</groupId>
        <artifactId>HikariCP</artifactId>
    </dependency>
</dependencies>

核心实现代码详解

1. 配置文件设置(application.yml)

spring:
  datasource:
    # 主库配置(写操作)
    master:
      jdbc-url: jdbc:mysql://localhost:3306/master_db?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
      username: root
      password: master_password
      driver-class-name: com.mysql.cj.jdbc.Driver
      # Hikari连接池配置
      hikari:
        maximum-pool-size: 10
        minimum-idle: 5
        idle-timeout: 30000
        max-lifetime: 1800000
        connection-timeout: 30000
        connection-test-query: SELECT 1
    # 从库配置(读操作)
    slave:
      jdbc-url: jdbc:mysql://localhost:3306/slave_db?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
      username: root
      password: slave_password
      driver-class-name: com.mysql.cj.jdbc.Driver
      hikari:
        maximum-pool-size: 15  # 从库可以配置更多连接,因为读操作通常更频繁
        minimum-idle: 8
        idle-timeout: 30000
        max-lifetime: 1800000
        connection-timeout: 30000
        connection-test-query: SELECT 1

2. 数据源枚举定义

/**
 * 数据源类型枚举
 * 用于标识当前操作应使用主库还是从库
 */
public enum DataSourceType {
    MASTER,  // 主库:用于写操作(INSERT、UPDATE、DELETE)
    SLAVE    // 从库:用于读操作(SELECT)
}

3. 数据源上下文管理器

/**
 * 数据源上下文管理器(基于ThreadLocal实现线程隔离)
 * 功能:保存当前线程使用的数据源类型,确保多线程环境下数据源切换不会相互干扰
 */
public class DataSourceContextHolder {
    
    // 使用ThreadLocal保证线程安全,每个线程有独立的数据源上下文
    private static final ThreadLocal CONTEXT_HOLDER = new ThreadLocal();
    
    /**
     * 设置当前线程的数据源类型
     * @param dataSourceType 数据源类型(MASTER或SLAVE)
     */
    public static void setDataSourceType(DataSourceType dataSourceType) {
        CONTEXT_HOLDER.set(dataSourceType);
    }
    
    /**
     * 获取当前线程的数据源类型
     * @return 当前数据源类型,默认为MASTER(保证写操作可靠性)
     */
    public static DataSourceType getDataSourceType() {
        return CONTEXT_HOLDER.get() == null ? DataSourceType.MASTER : CONTEXT_HOLDER.get();
    }
    
    /**
     * 清除当前线程的数据源类型
     * 防止内存泄漏,特别是在线程池场景下
     */
    public static void clearDataSourceType() {
        CONTEXT_HOLDER.remove();
    }
}

4. 动态路由数据源

/**
 * 动态路由数据源(继承Spring的AbstractRoutingDataSource)
 * 核心功能:根据当前上下文动态选择主库或从库
 */
public class DynamicRoutingDataSource extends AbstractRoutingDataSource {
    
    /**
     * 决定当前数据源查找键(Spring在每次数据库操作前调用此方法)
     * @return 数据源查找键(MASTER或SLAVE)
     */
    @Override
    protected Object determineCurrentLookupKey() {
        DataSourceType dataSourceType = DataSourceContextHolder.getDataSourceType();
        System.out.println(\"当前使用的数据源: \" + dataSourceType);
        return dataSourceType;
    }
}

5. 数据源配置类

/**
 * 数据源配置类(核心配置)
 * 配置主从数据源并初始化路由数据源
 */
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
    basePackages = \"com.example.repository\",
    entityManagerFactoryRef = \"entityManagerFactory\",
    transactionManagerRef = \"transactionManager\"
)
public class DataSourceConfig {
    
    /**
     * 主库数据源(写操作)
     */
    @Bean(name = \"masterDataSource\")
    @ConfigurationProperties(prefix = \"spring.datasource.master\")
    public DataSource masterDataSource() {
        return DataSourceBuilder.create().build();
    }
    
    /**
     * 从库数据源(读操作)
     */
    @Bean(name = \"slaveDataSource\")
    @ConfigurationProperties(prefix = \"spring.datasource.slave\")
    public DataSource slaveDataSource() {
        return DataSourceBuilder.create().build();
    }
    
    /**
     * 动态路由数据源(优先级最高,作为主数据源)
     */
    @Primary
    @Bean(name = \"routingDataSource\")
    public DataSource routingDataSource(
            @Qualifier(\"masterDataSource\") DataSource masterDataSource,
            @Qualifier(\"slaveDataSource\") DataSource slaveDataSource) {
        
        DynamicRoutingDataSource routingDataSource = new DynamicRoutingDataSource();
        
        // 配置目标数据源映射
        Map<Object, Object> targetDataSources = new HashMap();
        targetDataSources.put(DataSourceType.MASTER, masterDataSource);
        targetDataSources.put(DataSourceType.SLAVE, slaveDataSource);
        
        routingDataSource.setTargetDataSources(targetDataSources);
        routingDataSource.setDefaultTargetDataSource(masterDataSource); // 默认使用主库
        
        return routingDataSource;
    }
    
    /**
     * 实体管理器工厂
     */
    @Bean(name = \"entityManagerFactory\")
    public LocalContainerEntityManagerFactoryBean entityManagerFactory(
            EntityManagerFactoryBuilder builder, 
            @Qualifier(\"routingDataSource\") DataSource dataSource) {
        return builder
            .dataSource(dataSource)
            .packages(\"com.example.entity\")
            .persistenceUnit(\"mysqlUnit\")
            .build();
    }
    
    /**
     * 事务管理器
     */
    @Bean(name = \"transactionManager\")
    public PlatformTransactionManager transactionManager(
            @Qualifier(\"entityManagerFactory\") EntityManagerFactory entityManagerFactory) {
        return new JpaTransactionManager(entityManagerFactory);
    }
}

6. AOP切面实现自动路由

/**
 * 数据源切面配置(基于AOP自动切换数据源)
 * 通过方法名自动识别读写操作,实现数据源动态路由
 */
@Aspect
@Component
@Order(1) // 确保在事务切面之前执行
public class DataSourceAspect {
    
    /**
     * 写操作切点(insert、update、delete、save开头的方法)
     */
    @Before(\"execution(* com.example.service..*.create*(..)) || \" +
            \"execution(* com.example.service..*.update*(..)) || \" +
            \"execution(* com.example.service..*.delete*(..)) || \" +
            \"execution(* com.example.service..*.save*(..))\")
    public void setWriteDataSourceType() {
        DataSourceContextHolder.setDataSourceType(DataSourceType.MASTER);
        System.out.println(\"切换到主库(写操作)\");
    }
    
    /**
     * 读操作切点(select、get、find、query开头的方法)
     */
    @Before(\"execution(* com.example.service..*.select*(..)) || \" +
            \"execution(* com.example.service..*.get*(..)) || \" +
            \"execution(* com.example.service..*.find*(..)) || \" +
            \"execution(* com.example.service..*.query*(..))\")
    public void setReadDataSourceType() {
        DataSourceContextHolder.setDataSourceType(DataSourceType.SLAVE);
        System.out.println(\"切换到从库(读操作)\");
    }
    
    /**
     * 后置处理:清理数据源上下文
     */
    @After(\"execution(* com.example.service..*.*(..))\")
    public void clearDataSourceType() {
        DataSourceContextHolder.clearDataSourceType();
        System.out.println(\"清理数据源上下文\");
    }
}

7. 业务层使用示例

/**
 * 用户服务实现类
 * 演示读写分离的实际应用
 */
@Service
@Transactional
public class UserServiceImpl implements UserService {
    
    @Autowired
    private UserRepository userRepository;
    
    /**
     * 新增用户(写操作自动路由到主库)
     */
    @Override
    public User createUser(User user) {
        // 方法名以\"create\"开头,AOP会自动切换到MASTER数据源
        return userRepository.save(user);
    }
    
    /**
     * 根据ID查询用户(读操作自动路由到从库)
     */
    @Override
    @Transactional(readOnly = true) // 只读事务优化性能
    public User getUserById(Long id) {
        // 方法名以\"get\"开头,AOP会自动切换到SLAVE数据源
        return userRepository.findById(id).orElse(null);
    }
    
    /**
     * 查询所有用户(读操作)
     */
    @Override
    @Transactional(readOnly = true)
    public List<User> getAllUsers() {
        return userRepository.findAll();
    }
    
    /**
     * 更新用户信息(写操作)
     */
    @Override
    public User updateUser(User user) {
        return userRepository.save(user);
    }
}

测试与验证

单元测试类

/**
 * 读写分离测试类
 */
@SpringBootTest
class ReadWriteSeparationTest {
    
    @Autowired
    private UserService userService;
    
    /**
     * 测试写操作(应路由到主库)
     */
    @Test
    void testWriteOperation() {
        User user = new User();
        user.setUsername(\"testUser\");
        user.setPassword(\"password\");
        
        User savedUser = userService.createUser(user);
        
        Assertions.assertNotNull(savedUser.getId());
        System.out.println(\"写操作测试通过(路由到主库)\");
    }
    
    /**
     * 测试读操作(应路由到从库)
     */
    @Test
    void testReadOperation() {
        List users = userService.getAllUsers();
        
        Assertions.assertNotNull(users);
        System.out.println(\"读操作测试通过(路由到从库)\");
    }
    
    /**
     * 测试读写混合操作
     */
    @Test
    void testReadWriteMix() {
        // 写操作
        User user = new User();
        user.setUsername(\"mixUser\");
        userService.createUser(user);
        
        // 读操作
        User foundUser = userService.getUserById(1L);
        
        Assertions.assertNotNull(foundUser);
        System.out.println(\"读写混合操作测试通过\");
    }
}

️ 关键注意事项

1. 主从同步延迟处理

在读写分离架构中,主从同步存在延迟可能性。刚写入主库的数据可能不会立即在从库中可用。

解决方案:

/**
 * 强制读主库的场景
 */
@Service
public class CriticalService {
    
    @Autowired
    private UserRepository userRepository;
    
    /**
     * 重要业务:写入后立即读取,强制走主库
     */
    public User createAndGetUser(User user) {
        // 写入主库
        User savedUser = userRepository.save(user);
        
        // 强制从主库读取(避免同步延迟)
        DataSourceContextHolder.setDataSourceType(DataSourceType.MASTER);
        try {
            return userRepository.findById(savedUser.getId()).orElse(null);
        } finally {
            DataSourceContextHolder.clearDataSourceType();
        }
    }
}

2. 事务中的数据处理

在事务中,所有操作应使用同一数据源。

解决方案:

@Service
public class TransactionalService {
    
    /**
     * 事务内强制使用主库
     */
    @Transactional
    public void complexBusinessOperation() {
        // 方法开始时显式设置主库,确保事务内一致性
        DataSourceContextHolder.setDataSourceType(DataSourceType.MASTER);
        
        try {
            // 一系列数据库操作...
            // 所有这些操作都在同一事务中,使用同一数据源
        } finally {
            // 事务结束后清理
            DataSourceContextHolder.clearDataSourceType();
        }
    }
}

方案优缺点分析

优势 挑战 应对策略
提升读性能:将读请求分发到从库 主从同步延迟 关键业务强制读主库
提高可用性:主库故障时从库可读 事务内数据源一致性 事务中强制使用主库
减轻主库压力 复杂SQL路由 明确的读写操作分离

总结

通过以上完整的Spring Boot 3实现方案,你可以成功配置MySQL读写分离。关键在于理解动态数据源路由原理,合理处理主从同步延迟和事务一致性等挑战。这种架构能显著提升系统性能,特别适合读多写少的应用场景。

收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

申明:本文由第三方发布,内容仅代表作者观点,与本网站无关。对本文以及其中全部或者部分内容的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。本网发布或转载文章出于传递更多信息之目的,并不意味着赞同其观点或证实其描述,也不代表本网对其真实性负责。

左子网 开发教程 ️ Spring Boot 3实现MySQL读写分离完整指南 https://www.zuozi.net/3599.html

常见问题
  • 1、自动:拍下后,点击(下载)链接即可下载;2、手动:拍下后,联系卖家发放即可或者联系官方找开发者发货。
查看详情
  • 1、源码默认交易周期:手动发货商品为1-3天,并且用户付款金额将会进入平台担保直到交易完成或者3-7天即可发放,如遇纠纷无限期延长收款金额直至纠纷解决或者退款!;
查看详情
  • 1、描述:源码描述(含标题)与实际源码不一致的(例:货不对板); 2、演示:有演示站时,与实际源码小于95%一致的(但描述中有”不保证完全一样、有变化的可能性”类似显著声明的除外); 3、发货:不发货可无理由退款; 4、安装:免费提供安装服务的源码但卖家不履行的; 5、收费:价格虚标,额外收取其他费用的(但描述中有显著声明或双方交易前有商定的除外); 6、其他:如质量方面的硬性常规问题BUG等。 注:经核实符合上述任一,均支持退款,但卖家予以积极解决问题则除外。
查看详情
  • 1、左子会对双方交易的过程及交易商品的快照进行永久存档,以确保交易的真实、有效、安全! 2、左子无法对如“永久包更新”、“永久技术支持”等类似交易之后的商家承诺做担保,请买家自行鉴别; 3、在源码同时有网站演示与图片演示,且站演与图演不一致时,默认按图演作为纠纷评判依据(特别声明或有商定除外); 4、在没有”无任何正当退款依据”的前提下,商品写有”一旦售出,概不支持退款”等类似的声明,视为无效声明; 5、在未拍下前,双方在QQ上所商定的交易内容,亦可成为纠纷评判依据(商定与描述冲突时,商定为准); 6、因聊天记录可作为纠纷评判依据,故双方联系时,只与对方在左子上所留的QQ、手机号沟通,以防对方不承认自我承诺。 7、虽然交易产生纠纷的几率很小,但一定要保留如聊天记录、手机短信等这样的重要信息,以防产生纠纷时便于左子介入快速处理。
查看详情

相关文章

猜你喜欢
发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务