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 性能优化建议

  1. 合理配置连接池:主从库连接池参数要根据读写比例调整
  2. SQL优化:确保SQL语句能够有效利用索引
  3. 缓存策略:结合Redis等缓存,减少数据库压力
  4. 监控告警:监控主从延迟、连接池使用情况等

6.2 数据一致性保障

  1. 事务边界:跨数据源操作需要分布式事务
  2. 主从延迟处理:写入后立即查询可能需要强制走主库
  3. 幂等性设计:防止重复操作导致数据不一致

6.3 安全性考虑

  1. 租户数据隔离:确保租户间数据完全隔离
  2. 权限控制:不同租户只能访问自己的数据
  3. 审计日志:记录数据访问和操作日志

七、总结

SpringBoot + MyBatis-Plus 的多数据源架构为现代SaaS应用提供了强大的支撑:

  • 读写分离:有效提升系统性能和可用性
  • 多租户架构:实现数据隔离,降低运营成本
  • 灵活配置:通过注解轻松切换数据源
  • 自动SQL拼接:多租户条件自动添加,减少出错

记住:架构没有绝对的好坏,关键在于是否适合业务场景。选择合适的多租户模式和读写分离策略,才能让你的SaaS平台既安全又高效!

关注服务端技术精选,获取更多后端实战干货!

你在多数据源或SaaS架构实践中遇到过哪些挑战?欢迎在评论区分享你的经验!


标题:SpringBoot + MyBatis-Plus + 多数据源:主从读写分离与多租户 SaaS 架构实战
作者:jiangyi
地址:http://www.jiangyi.space/articles/2025/12/24/1766557455892.html

    0 评论
avatar