Skip to content

租户 (tenant)

最后更新: 8 天前
实践版本: v2.13.4

简介

continew-starter-extension-tenant-mp 是 ContiNew Starter 提供的基于 MyBatis Plus 实现的多租户扩展模块,支持行级和数据源级两种租户隔离策略,提供开箱即用的 SaaS 多租户解决方案。通过简单的配置和注解,可快速实现租户数据隔离,保障不同租户数据的安全性和独立性。

主要特性

  • 双重隔离策略: 支持行级隔离和数据源级隔离两种方式,满足不同场景需求
  • 无侵入性: 基于 MyBatis Plus 拦截器,对业务代码无侵入
  • 上下文管理: 基于 ThreadLocal 的租户上下文管理,支持线程间传递

隔离策略

行级隔离(LINE)

在 SQL 查询时自动添加租户 ID 条件,所有租户数据存储在同一个数据库中,通过租户 ID 字段进行数据隔离。

优点:

  • 资源利用率高,维护成本低
  • 数据迁移和备份简单
  • 适合租户数量多但数据量适中的场景

缺点:

  • 数据安全性相对较低
  • 单表数据量大时查询性能可能受影响

数据源级隔离(DATASOURCE)

每个租户使用独立的数据库,通过动态切换数据源实现隔离。

优点:

  • 数据安全性高,租户间完全隔离
  • 单租户性能优异
  • 支持租户定制化需求

缺点:

  • 资源消耗大,维护成本高
  • 数据迁移和跨租户查询复杂

快速开始

引入依赖

pom.xml
xml
<dependency>
    <groupId>top.continew.starter</groupId>
    <artifactId>continew-starter-extension-tenant-mp</artifactId>
</dependency>

添加配置

配置详情请查看:top.continew.starter.extension.tenant.autoconfigure.TenantProperties

application.yml
yaml
--- ### 租户配置
continew-starter.tenant:
  enabled: true
  # 隔离级别(默认:LINE,行级)
  isolation-level: LINE
  # 租户 ID 列名
  tenant-id-column: tenant_id
  # 请求头中租户 ID 键名
  tenant-id-header: X-Tenant-Id
  # 忽略表(忽略拼接租户条件)
  ignore-tables:
    - tenant # 租户表
    - tenant_package # 租户套餐表
    - tenant_package_menu # 租户套餐与菜单关联表
    - gen_config # 代码生成配置表
    - gen_field_config # 代码生成字段配置表
    - sys_menu # 菜单表
    - sys_dict # 字典表
    - sys_dict_item # 字典项表
    - sys_option # 参数表
    - sys_storage # 存储表
    - sys_sms_config # 短信配置表
    - sys_sms_log # 短信日志表
    - sys_client # 客户端表
    - sys_app # 应用表

实现 TenantProvider

创建租户提供者实现类,用于根据租户 ID 获取租户上下文信息:

java
import top.continew.starter.extension.tenant.config.TenantProvider;
import top.continew.starter.extension.tenant.context.TenantContext;
import top.continew.starter.extension.tenant.enums.TenantIsolationLevel;

/**
 * 默认租户提供者
 *
 * @author Charles7c
 */
@Component
public class DefaultTenantProvider implements TenantProvider {

    @Override
    public TenantContext getByTenantId(String tenantIdAsString, boolean verify) {
        TenantContext context = new TenantContext();
        // 隔离级别(数据源级隔离和行级隔离混用时需要)
        context.setIsolationLevel(TenantIsolationLevel.LINE);
        // 设置租户 ID
        if (StrUtil.isNotBlank(tenantIdAsString)) {
            context.setTenantId(Long.parseLong(tenantIdAsString));
        	return context;
        }
        // 自定义校验及处理...
        return context;
    }
}

数据库表设计

行级隔离

对于行级隔离,需要在相关表中添加租户 ID 字段:

sql
CREATE TABLE `sys_user` (
  `id`          bigint(20)   NOT NULL AUTO_INCREMENT COMMENT '用户ID',
  `username`    varchar(64)  NOT NULL                COMMENT '用户名',
  `create_time` datetime     NOT NULL                COMMENT '创建时间',
  `update_time` datetime     NOT NULL                COMMENT '修改时间',
  `tenant_id`   bigint(20)   NOT NULL                COMMENT '租户ID',
  PRIMARY KEY (`id`),
  INDEX `idx_tenant_id` (`tenant_id`)
) ENGINE=InnoDB COMMENT='用户表';

数据源级隔离

数据源级隔离时,每个租户使用独立的数据库,表结构不需要租户 ID 字段。

前端处理

前端发起请求时,在请求头中携带 X-Tenant-Id,后端租户拦截器会根据该字段解析租户 ID 值,并进行租户上下文构建(TenantProvider)。随后在相关数据库操作时,会进行租户隔离操作。

例如:根据用户名查询用户,对应 SQL 经过租户隔离处理后为:

sql
select * from sys_user where username = 'zhangsan' and tenant_id = 1

忽略租户处理

如果不需要租户隔离,可在对应方法或类上添加 @TenantIgnore 注解。

java
@RestController
@RequestMapping("/system")
public class SystemController {
    
    /**
     * 获取系统配置(忽略租户隔离)
     */
    @TenantIgnore
    @GetMapping("/config")
    public R<SystemConfig> getSystemConfig() {
        // 该方法不会添加租户条件
        return R.ok(systemService.getConfig());
    }
}

如果只是方法内局部逻辑需要忽略租户隔离,可以使用 TenantUtilsexecuteIgnore 方法。

java
TenantUtils.executeIgnore(() -> {
   // 代码逻辑 
});

指定租户处理

如果需要在指定租户中执行某些逻辑,可以使用 TenantUtilsexecute 方法。

java
TenantUtils.execute(tenantId, () -> {
   // 代码逻辑 
});

核心依赖

依赖描述
top.continew.starter:continew-starter-extension-tenant-core多租户 - 核心模块
com.baomidou:mybatis-plus-jsqlparserMyBatis Plus jsqlparser 适配(包含原扩展模块内容)
com.baomidou:dynamic-datasource-spring-boot3-starter(optional)Dynamic Datasource(基于 Spring Boot 的快速集成多数据源的启动器)

参考资料

  1. 多租户 | MyBatis-Plus:https://mybatis.plus/guide/interceptor-tenant-line.html