Spring Boot项目优雅实现读写分离

文章目录

    • [1. 读写分离简介](#1. 读写分离简介)
    • [2. Spring Boot集成MyBatis](#2. Spring Boot集成MyBatis)
    • [3. 配置读写分离数据源](#3. 配置读写分离数据源)
    • [4. 定义数据源上下文](#4. 定义数据源上下文)
    • [5. 自定义注解和切面](#5. 自定义注解和切面)
    • [6. 在Service层使用注解](#6. 在Service层使用注解)
    • [7. 拓展与分析](#7. 拓展与分析)
      • [7.1 多数据源的选择](#7.1 多数据源的选择)
      • [7.2 事务的处理](#7.2 事务的处理)
      • [7.3 异常处理](#7.3 异常处理)
      • [7.4 动态数据源切换](#7.4 动态数据源切换)
      • [7.5 Spring Boot版本适配](#7.5 Spring Boot版本适配)

🎉欢迎来到架构设计专栏~Spring Boot项目优雅实现读写分离



Spring Boot作为一种快速开发框架,广泛应用于Java项目中。在一些大型应用中,数据库的读写分离是提升性能和扩展性的一种重要手段。本文将介绍如何在Spring Boot项目中优雅地实现读写分离,并通过适当的代码插入,详细展开实现步骤,同时进行拓展和分析。

1. 读写分离简介

读写分离是指在数据库集群中,将数据库的读操作和写操作分别分配到不同的节点上。这样可以充分利用多台服务器的资源,提高系统的并发处理能力。一般来说,写操作相对读操作更为耗时,通过读写分离,可以有效减轻主库的负担。

2. Spring Boot集成MyBatis

首先,我们需要在Spring Boot项目中集成MyBatis,作为数据库访问的ORM框架。可以通过在pom.xml文件中添加依赖来引入MyBatis和MyBatis-Spring-Boot-Starter:

xml 复制代码
<!-- MyBatis依赖 -->
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.2.0</version>
</dependency>

<!-- 数据库连接池依赖(这里以HikariCP为例) -->
<dependency>
    <groupId>com.zaxxer</groupId>
    <artifactId>HikariCP</artifactId>
</dependency>

然后,配置application.properties文件,指定数据库连接信息:

properties 复制代码
# 主库
spring.datasource.master.url=jdbc:mysql://master-host:3306/master_db
spring.datasource.master.username=root
spring.datasource.master.password=root
# 从库
spring.datasource.slave.url=jdbc:mysql://slave-host:3306/slave_db
spring.datasource.slave.username=root
spring.datasource.slave.password=root

3. 配置读写分离数据源

在实现读写分离前,我们需要定义一个数据源路由器,用于根据不同的操作选择不同的数据源。在Spring Boot中,可以通过AbstractRoutingDataSource实现这一功能。以下是一个简单的数据源路由器示例:

java 复制代码
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

public class RoutingDataSource extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceContextHolder.getDataSourceType();
    }
}

在上述代码中,determineCurrentLookupKey方法用于确定当前使用的数据源,DataSourceContextHolder是一个自定义的上下文持有类,用于存储当前线程使用的数据源类型。接下来,我们需要在配置类中配置这个数据源路由器:

java 复制代码
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

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

    @Bean(name = "routingDataSource")
    public RoutingDataSource routingDataSource(
            @Qualifier("masterDataSource") DataSource masterDataSource,
            @Qualifier("slaveDataSource") DataSource slaveDataSource) {
        RoutingDataSource routingDataSource = new RoutingDataSource();
        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
    public PlatformTransactionManager transactionManager(DataSource routingDataSource) {
        return new DataSourceTransactionManager(routingDataSource);
    }
}

在上述代码中,我们配置了两个数据源,一个用于主库,一个用于从库。然后,通过routingDataSource方法创建了一个RoutingDataSource实例,将主库和从库加入到数据源路由器中,并设置默认数据源为主库。

4. 定义数据源上下文

接下来,我们需要定义一个数据源上下文类,用于在当前线程中保存和获取当前使用的数据源类型。这个上下文类应该是线程安全的,因为它会在多个线程中被访问。

java 复制代码
public class DataSourceContextHolder {

    private static final ThreadLocal<DataSourceType> contextHolder = new ThreadLocal<>();

    public static void setDataSourceType(DataSourceType dataSourceType) {
        contextHolder.set(dataSourceType);
    }

    public static DataSourceType getDataSourceType() {
        return contextHolder.get();
    }

    public static void clearDataSourceType() {
        contextHolder.remove();
    }
}

5. 自定义注解和切面

为了在Service层标注读操作和写操作,我们可以定义两个自定义注解@Master@Slave,并创建一个切面DataSourceAspect,通过AOP切入点拦截被这两个注解标记的方法,然后在方法执行前设置数据源类型。

java 复制代码
@Aspect
@Component
public class DataSourceAspect {

    @Before("@annotation(master)")
    public void setMasterDataSource(JoinPoint joinPoint, Master master) {
        DataSource

ContextHolder.setDataSourceType(DataSourceType.MASTER);
    }

    @Before("@annotation(slave)")
    public void setSlaveDataSource(JoinPoint joinPoint, Slave slave) {
        DataSourceContextHolder.setDataSourceType(DataSourceType.SLAVE);
    }

    @After("@annotation(master) || @annotation(slave)")
    public void clearDataSourceType(JoinPoint joinPoint, Master master, Slave slave) {
        DataSourceContextHolder.clearDataSourceType();
    }
}

在上述代码中,通过@Before注解定义了两个切入点,分别拦截被@Master@Slave注解标记的方法,在方法执行前设置对应的数据源类型。在@After注解中清除当前线程的数据源类型。

6. 在Service层使用注解

最后,在Service层需要进行读写分离的方法上使用定义好的注解,标记读操作和写操作。以下是一个示例:

java 复制代码
@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;

    @Master
    public User getUserById(int userId) {
        return userMapper.selectById(userId);
    }

    @Slave
    public List<User> listAllUsers() {
        return userMapper.selectAll();
    }

    @Master
    public void saveUser(User user) {
        userMapper.insert(user);
    }
}

在上述示例中,@Master@Slave注解分别用于标记读操作和写操作的方法。在实际应用中,根据具体需求和业务场景进行灵活使用。

7. 拓展与分析

7.1 多数据源的选择

上述示例中使用了两个数据源,一个用于主库,一个用于从库。在实际应用中,如果有多个从库,可以在配置类中配置多个从库数据源,然后在数据源路由器中动态选择。

7.2 事务的处理

在涉及到事务的场景中,需要注意对事务的处理。在使用读写分离的情况下,一般将写操作放在事务中,而读操作不放在事务中。因为事务一般需要使用主库,而从库主要用于读取操作,不参与事务的提交与回滚。

7.3 异常处理

在使用读写分离的过程中,可能会遇到主从同步延迟导致的数据不一致问题。因此,在涉及到数据一致性要求较高的业务场景中,需要谨慎使用读写分离,考虑一些其他的解决方案,如数据的多版本控制等。

7.4 动态数据源切换

上述示例中使用AOP切面在方法执行前设置数据源类型。在某些场景中,可能需要在代码中动态切换数据源,这时可以通过编程式的方式设置数据源类型,而不是依赖AOP。

7.5 Spring Boot版本适配

请注意根据使用的Spring Boot版本来选择相应的依赖版本。在示例中,使用的MyBatis版本是2.2.0,如果使用的是较新的Spring Boot版本,建议查阅官方文档或相关依赖库的最新版本。

通过上述步骤,我们完成了Spring Boot项目中读写分离的优雅实现。通过合理的代码插入,详细展开了每个步骤的实现,并对一些拓展和分析进行了说明。希望这篇文章对正在进行数据库优化的开发者有所帮助。


🧸结尾 ❤️ 感谢您的支持和鼓励! 😊🙏

📜您可能感兴趣的内容:

相关推荐
等一场春雨3 分钟前
Java设计模式 八 适配器模式 (Adapter Pattern)
java·设计模式·适配器模式
一弓虽24 分钟前
java基础学习——jdbc基础知识详细介绍
java·学习·jdbc·连接池
王磊鑫25 分钟前
Java入门笔记(1)
java·开发语言·笔记
马剑威(威哥爱编程)1 小时前
2025春招 SpringCloud 面试题汇总
后端·spring·spring cloud
硬件人某某某1 小时前
Java基于SSM框架的社区团购系统小程序设计与实现(附源码,文档,部署)
java·开发语言·社区团购小程序·团购小程序·java社区团购小程序
程序员徐师兄1 小时前
Java 基于 SpringBoot 的校园外卖点餐平台微信小程序(附源码,部署,文档)
java·spring boot·微信小程序·校园外卖点餐·外卖点餐小程序·校园外卖点餐小程序
chengpei1471 小时前
chrome游览器JSON Formatter插件无效问题排查,FastJsonHttpMessageConverter导致Content-Type返回不正确
java·前端·chrome·spring boot·json
Quantum&Coder1 小时前
Objective-C语言的计算机基础
开发语言·后端·golang
五味香1 小时前
Java学习,List 元素替换
android·java·开发语言·python·学习·golang·kotlin
Joeysoda1 小时前
Java数据结构 (从0构建链表(LinkedList))
java·linux·开发语言·数据结构·windows·链表·1024程序员节