租户 (tenant)
简介
continew-starter-extension-tenant-mp
是 ContiNew Starter 提供的基于 MyBatis Plus 实现的多租户扩展模块,支持行级和数据源级两种租户隔离策略,提供开箱即用的 SaaS 多租户解决方案。通过简单的配置和注解,可快速实现租户数据隔离,保障不同租户数据的安全性和独立性。
主要特性
- 双重隔离策略: 支持行级隔离和数据源级隔离两种方式,满足不同场景需求
- 无侵入性: 基于 MyBatis Plus 拦截器,对业务代码无侵入
- 上下文管理: 基于 ThreadLocal 的租户上下文管理,支持线程间传递
隔离策略
行级隔离(LINE)
在 SQL 查询时自动添加租户 ID 条件,所有租户数据存储在同一个数据库中,通过租户 ID 字段进行数据隔离。
优点:
- 资源利用率高,维护成本低
- 数据迁移和备份简单
- 适合租户数量多但数据量适中的场景
缺点:
- 数据安全性相对较低
- 单表数据量大时查询性能可能受影响
数据源级隔离(DATASOURCE)
每个租户使用独立的数据库,通过动态切换数据源实现隔离。
优点:
- 数据安全性高,租户间完全隔离
- 单租户性能优异
- 支持租户定制化需求
缺点:
- 资源消耗大,维护成本高
- 数据迁移和跨租户查询复杂
快速开始
引入依赖
<dependency>
<groupId>top.continew.starter</groupId>
<artifactId>continew-starter-extension-tenant-mp</artifactId>
</dependency>
添加配置
配置详情请查看:top.continew.starter.extension.tenant.autoconfigure.TenantProperties
。
--- ### 租户配置
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 获取租户上下文信息:
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 字段:
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 经过租户隔离处理后为:
select * from sys_user where username = 'zhangsan' and tenant_id = 1
忽略租户处理
如果不需要租户隔离,可在对应方法或类上添加 @TenantIgnore
注解。
@RestController
@RequestMapping("/system")
public class SystemController {
/**
* 获取系统配置(忽略租户隔离)
*/
@TenantIgnore
@GetMapping("/config")
public R<SystemConfig> getSystemConfig() {
// 该方法不会添加租户条件
return R.ok(systemService.getConfig());
}
}
如果只是方法内局部逻辑需要忽略租户隔离,可以使用 TenantUtils
的 executeIgnore
方法。
TenantUtils.executeIgnore(() -> {
// 代码逻辑
});
指定租户处理
如果需要在指定租户中执行某些逻辑,可以使用 TenantUtils
的 execute
方法。
TenantUtils.execute(tenantId, () -> {
// 代码逻辑
});
核心依赖
依赖 | 描述 |
---|---|
top.continew.starter:continew-starter-extension-tenant-core | 多租户 - 核心模块 |
com.baomidou:mybatis-plus-jsqlparser | MyBatis Plus jsqlparser 适配(包含原扩展模块内容) |
com.baomidou:dynamic-datasource-spring-boot3-starter(optional) | Dynamic Datasource(基于 Spring Boot 的快速集成多数据源的启动器) |
参考资料
- 多租户 | MyBatis-Plus:https://mybatis.plus/guide/interceptor-tenant-line.html