SpringBoot + MyBatis-Plus + 多数据源:主从读写分离与多租户 SaaS 架构实战
一、开场白:为什么你的系统需要多数据源和多租户架构?
还记得第一次遇到数据库瓶颈时,运营反馈说:"系统响应太慢了,用户都在投诉!"仔细一查,发现读写操作都在一个数据库上,写操作一多,查询就慢得像蜗牛。更别提要做一个SaaS平台,需要为不同客户提供数据隔离了...
相信很多后端同学都遇到过类似的场景。随着业务快速发展,单数据源架构已经无法满足高并发、高可用的需求。今天咱们就聊聊,SpringBoot + MyBatis-Plus 如何实现多数据源的主从读写分离和多租户SaaS架构,让你的系统轻松应对高并发和多租户需求!
在SaaS(Software as a Service)模式下,一个应用需要为多个租户提供服务,每个租户的数据必须严格隔离。同时,为了提升系统性能,我们需要将读写操作分离到不同的数据库节点上。这两大需求,正是多数据源架构的核心应用场景。
二、读写分离:让数据库"分身有术"
2.1 为什么需要读写分离?
简单来说,就是写操作集中处理,读操作分散承担。当写操作较多时,会占用大量数据库资源,导致查询变慢。通过读写分离,我们可以:
- 提升查询性能:读操作分摊到多个从库
- 增强系统可用性:主库挂了,部分读操作可切换到从库
- 数据安全保障:从库可以作为备份,同时支持数据分析等操作
2.2 实现原理:AOP动态数据源路由
核心思路是通过AOP拦截数据库操作,根据方法特性动态切换数据源:
@Aspect
@Component
public class DataSourceRoutingAspect {
@Before("@annotation(ReadOnly)")
public void setReadDataSource() {
DataSourceContext.set(DataSourceType.SLAVE);
}
@Before("@annotation(WriteOnly)")
public void setWriteDataSource() {
DataSourceContext.set(DataSourceType.MASTER);
}
@After("@annotation(ReadOnly) || @annotation(WriteOnly)")
public void clearDataSource() {
DataSourceContext.clear();
}
}
2.3 配置实战:SpringBoot多数据源配置
在application.yml中配置主从数据源:
spring:
datasource:
dynamic:
primary: master # 默认数据源
strict: false # 严格模式,未配置的数据源是否抛出异常
datasource:
master:
url: jdbc:mysql://master-db:3306/app
username: root
password: xxx
driver-class-name: com.mysql.cj.jdbc.Driver
slave:
url: jdbc:mysql://slave-db:3306/app
username: root
password: xxx
driver-class-name: com.mysql.cj.jdbc.Driver
三、多租户架构:数据隔离的艺术
3.1 多租户实现模式:哪种适合你?
MyBatis-Plus支持三种多租户模式:
1. 独立数据库模式(DATASOURCE):
- 每个租户独立的数据库
- 隔离级别最高,安全性最好
- 成本高,维护复杂
2. 独立Schema模式(SCHEMA):
- 每个租户独立的Schema
- 隔离级别较高,成本适中
- 适用于中等规模SaaS
3. 字段隔离模式(COLUMN):
- 所有租户共享数据库和表,通过tenant_id字段区分
- 成本最低,扩展性最好
- 适用于大多数SaaS场景
3.2 MyBatis-Plus多租户实现
通过TenantLineInnerInterceptor实现字段级数据隔离:
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(new TenantHandler() {
@Override
public Expression getTenantId() {
return new LongValue(TenantContextHolder.getTenantId());
}
@Override
public String getTenantIdColumn() {
return "tenant_id";
}
@Override
public boolean ignoreTable(String tableName) {
// 某些表不需要租户隔离,如租户信息表
return "tenant_info".equalsIgnoreCase(tableName);
}
}));
return interceptor;
}
}
3.3 租户上下文管理
实现租户ID的上下文传递:
public class TenantContextHolder {
private static final ThreadLocal<Long> CONTEXT = new ThreadLocal<>();
public static void setTenantId(Long tenantId) {
CONTEXT.set(tenantId);
}
public static Long getTenantId() {
return CONTEXT.get();
}
public static void clear() {
CONTEXT.remove();
}
}
四、MyBatis-Plus多数据源集成
4.1 引入Dynamic-Datasource依赖
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>3.5.2</version>
</dependency>
4.2 动态数据源注解使用
通过@DS注解指定数据源:
@Service
public class UserService {
// 默认使用主库
public User getUserById(Long id) {
return userMapper.selectById(id);
}
// 指定使用从库
@DS("slave")
public List<User> getAllUsers() {
return userMapper.selectList(null);
}
// 指定使用主库
@DS("master")
@Transactional
public void createUser(User user) {
userMapper.insert(user);
}
}
4.3 多租户与读写分离结合
在实际应用中,我们可以将多租户和读写分离结合使用:
@Service
public class TenantService {
// 多租户 + 从库查询
@DS("slave")
public List<Order> getTenantOrders() {
// MyBatis-Plus会自动添加tenant_id条件
return orderMapper.selectList(new QueryWrapper<Order>()
.eq("status", "completed"));
}
// 多租户 + 主库写入
@DS("master")
@Transactional
public void createTenantOrder(Order order) {
// MyBatis-Plus会自动添加tenant_id条件
orderMapper.insert(order);
}
}
五、实战场景:电商SaaS平台
以一个电商SaaS平台为例,展示如何综合运用多数据源和多租户架构:
5.1 租户初始化
@Service
public class TenantInitService {
public void initTenant(Long tenantId) {
// 设置租户上下文
TenantContextHolder.setTenantId(tenantId);
// 初始化租户数据
TenantInfo tenantInfo = new TenantInfo();
tenantInfo.setId(tenantId);
tenantInfo.setName("租户" + tenantId);
tenantInfo.setCreateTime(new Date());
tenantInfoMapper.insert(tenantInfo);
}
}
5.2 商品查询优化
@Service
public class ProductService {
// 使用从库查询商品,自动添加租户条件
@DS("slave")
public List<Product> getTenantProducts() {
return productMapper.selectList(
new QueryWrapper<Product>()
.eq("status", 1) // 只查询上架商品
);
}
// 使用主库更新商品,自动添加租户条件
@DS("master")
@Transactional
public void updateProductStock(Long productId, Integer quantity) {
Product product = new Product();
product.setId(productId);
product.setStock(product.getStock() - quantity);
productMapper.updateById(product);
}
}
5.3 订单处理流程
@Service
public class OrderService {
@DS("master")
@Transactional
public void createOrder(Order order) {
// 1. 扣减库存(主库操作)
productService.updateProductStock(order.getProductId(), order.getQuantity());
// 2. 创建订单(主库操作,自动添加租户条件)
orderMapper.insert(order);
// 3. 更新用户积分(主库操作,自动添加租户条件)
userPointService.addPoints(order.getUserId(), order.getPoint());
}
// 查询订单列表,使用从库,自动添加租户条件
@DS("slave")
public List<Order> getUserOrders(Long userId) {
return orderMapper.selectList(
new QueryWrapper<Order>()
.eq("user_id", userId)
.orderByDesc("create_time")
);
}
}
六、最佳实践与注意事项
6.1 性能优化建议
- 合理配置连接池:主从库连接池参数要根据读写比例调整
- SQL优化:确保SQL语句能够有效利用索引
- 缓存策略:结合Redis等缓存,减少数据库压力
- 监控告警:监控主从延迟、连接池使用情况等
6.2 数据一致性保障
- 事务边界:跨数据源操作需要分布式事务
- 主从延迟处理:写入后立即查询可能需要强制走主库
- 幂等性设计:防止重复操作导致数据不一致
6.3 安全性考虑
- 租户数据隔离:确保租户间数据完全隔离
- 权限控制:不同租户只能访问自己的数据
- 审计日志:记录数据访问和操作日志
七、总结
SpringBoot + MyBatis-Plus 的多数据源架构为现代SaaS应用提供了强大的支撑:
- 读写分离:有效提升系统性能和可用性
- 多租户架构:实现数据隔离,降低运营成本
- 灵活配置:通过注解轻松切换数据源
- 自动SQL拼接:多租户条件自动添加,减少出错
记住:架构没有绝对的好坏,关键在于是否适合业务场景。选择合适的多租户模式和读写分离策略,才能让你的SaaS平台既安全又高效!
关注服务端技术精选,获取更多后端实战干货!
你在多数据源或SaaS架构实践中遇到过哪些挑战?欢迎在评论区分享你的经验!
标题:SpringBoot + MyBatis-Plus + 多数据源:主从读写分离与多租户 SaaS 架构实战
作者:jiangyi
地址:http://www.jiangyi.space/articles/2025/12/24/1766557455892.html
- 一、开场白:为什么你的系统需要多数据源和多租户架构?
- 二、读写分离:让数据库"分身有术"
- 2.1 为什么需要读写分离?
- 2.2 实现原理:AOP动态数据源路由
- 2.3 配置实战:SpringBoot多数据源配置
- 三、多租户架构:数据隔离的艺术
- 3.1 多租户实现模式:哪种适合你?
- 3.2 MyBatis-Plus多租户实现
- 3.3 租户上下文管理
- 四、MyBatis-Plus多数据源集成
- 4.1 引入Dynamic-Datasource依赖
- 4.2 动态数据源注解使用
- 4.3 多租户与读写分离结合
- 五、实战场景:电商SaaS平台
- 5.1 租户初始化
- 5.2 商品查询优化
- 5.3 订单处理流程
- 六、最佳实践与注意事项
- 6.1 性能优化建议
- 6.2 数据一致性保障
- 6.3 安全性考虑
- 七、总结