多租户模块(MyBatis Plus实现)
简介
continew-starter-extension-tenant-mp
是 ContiNew Starter 提供的基于 MyBatis Plus 实现的多租户扩展模块,支持行级和数据源级两种租户隔离策略,提供开箱即用的 SaaS 多租户解决方案。通过简单的配置和注解,可快速实现租户数据隔离,保障不同租户数据的安全性和独立性。
主要特性
- 双重隔离策略: 支持行级隔离和数据源级隔离两种方式,满足不同场景需求
- 透明化处理: 基于 MyBatis Plus 拦截器,对业务代码无侵入
- 动态数据源: 集成 Dynamic Datasource,支持租户级数据源动态切换
- 上下文管理: 基于 ThreadLocal 的租户上下文管理,支持线程间传递
- 灵活配置: 支持忽略特定表和超级租户等高级配置
- 注解支持: 提供
@TenantIgnore
注解,方便排除特定方法或类
隔离策略
行级隔离(LINE)
在 SQL 查询时自动添加租户 ID 条件,所有租户数据存储在同一个数据库中,通过租户 ID 字段进行数据隔离。
优点:
- 资源利用率高,维护成本低
- 数据迁移和备份简单
- 适合租户数量多但数据量适中的场景
缺点:
- 数据安全性相对较低
- 单表数据量大时查询性能可能受影响
数据源级隔离(DATASOURCE)
每个租户使用独立的数据库,通过动态切换数据源实现隔离。
优点:
- 数据安全性高,租户间完全隔离
- 单租户性能优异
- 支持租户定制化需求
缺点:
- 资源消耗大,维护成本高
- 数据迁移和跨租户查询复杂
使用步骤
引入依赖
在 pom.xml
中添加以下依赖:
<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(行级)、DATASOURCE(数据源级)
isolation-level: LINE
# 租户 ID 列名(默认:tenant_id)
tenant-id-column: tenant_id
# 请求头中租户 ID 键名(默认:X-Tenant-Id)
tenant-id-header: X-Tenant-Id
# 超级租户 ID(默认:1)
super-tenant-id: 1
# 多租户忽略的表
ignore-tables:
- gen_config # 代码生成
- gen_field_config
- sys_dict # 字典表
- sys_dict_item
- sys_option #参数
- sys_storage # 存储配置
- sys_tenant # 租户
- sys_tenant_package
- sys_tenant_db_connect
- sys_app #应用
- sys_client #客户端管理
- sys_sms_config
- sys_sms_log
实现 TenantProvider
创建租户提供者实现类,用于根据租户 ID 获取租户上下文信息:
package top.continew.sample.config;
import org.springframework.stereotype.Component;
import top.continew.starter.extension.tenant.config.TenantDataSource;
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 tenantId, boolean isVerify) {
// 根据租户 ID 查询租户信息(这里仅作示例)
TenantContext context = new TenantContext();
context.setTenantId(Long.valueOf(tenantId));
// 行级隔离示例
context.setIsolationLevel(TenantIsolationLevel.LINE);
// 数据源级隔离示例(需要配置租户专属数据源)
if ("2".equals(tenantId)) {
context.setIsolationLevel(TenantIsolationLevel.DATASOURCE);
TenantDataSource dataSource = new TenantDataSource();
dataSource.setUrl("jdbc:mysql://localhost:3306/tenant_2");
dataSource.setUsername("root");
dataSource.setPassword("123456");
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
context.setDataSource(dataSource);
}
return context;
}
}
数据库表设计
行级隔离表设计
对于行级隔离,需要在相关表中添加租户 ID 字段:
CREATE TABLE `user` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '用户ID',
`username` varchar(64) NOT NULL COMMENT '用户名',
`email` varchar(128) DEFAULT NULL COMMENT '邮箱',
`tenant_id` bigint NOT NULL COMMENT '租户ID',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
PRIMARY KEY (`id`),
KEY `idx_tenant_id` (`tenant_id`)
) ENGINE=InnoDB COMMENT='用户表';
数据源级隔离
数据源级隔离时,每个租户使用独立的数据库,表结构不需要租户 ID 字段。
Web 层集成
多租户模块会自动从请求头中获取租户 ID,并设置到租户上下文中。默认的请求头键名为 X-Tenant-Id
,可通过配置修改。
// 前端请求示例
fetch('/api/users', {
headers: {
'X-Tenant-Id': '1001',
'Content-Type': 'application/json'
}
})
使用注解
@TenantIgnore
用于忽略多租户处理的方法或类:
@RestController
@RequestMapping("/system")
public class SystemController {
/**
* 获取系统配置(忽略租户隔离)
*/
@TenantIgnore
@GetMapping("/config")
public R<SystemConfig> getSystemConfig() {
// 该方法不会添加租户条件
return R.ok(systemService.getConfig());
}
}
/**
* 系统配置 Service(整个类忽略租户隔离)
*/
@TenantIgnore
@Service
public class SystemConfigService {
public SystemConfig getConfig() {
// 该类的所有方法都不会添加租户条件
return configMapper.selectOne(null);
}
}
编程式调用
可以通过 TenantHandler 在代码中手动切换租户上下文:
@Service
public class UserService {
@Autowired
private TenantHandler tenantHandler;
@Autowired
private UserMapper userMapper;
/**
* 跨租户查询用户数据
*/
public void crossTenantQuery(Long targetTenantId) {
// 在指定租户上下文中执行操作
tenantHandler.execute(targetTenantId, () -> {
List<User> users = userMapper.selectList(null);
// 处理用户数据
});
}
}
核心组件
TenantProperties
租户配置属性类,定义多租户相关配置:
@ConfigurationProperties(PropertiesConstants.TENANT)
public class TenantProperties {
/**
* 是否启用
*/
private boolean enabled = true;
/**
* 租户隔离级别
*/
private TenantIsolationLevel isolationLevel = TenantIsolationLevel.LINE;
/**
* 租户 ID 列名
*/
private String tenantIdColumn = "tenant_id";
/**
* 请求头中租户 ID 键名
*/
private String tenantIdHeader = "X-Tenant-Id";
/**
* 超级租户 ID
*/
private Long superTenantId = 1L;
/**
* 忽略表(忽略拼接多租户条件)
*/
private List<String> ignoreTables;
}
TenantIsolationLevel
租户隔离级别枚举:
public enum TenantIsolationLevel {
/**
* 行级
*/
LINE,
/**
* 数据源级
*/
DATASOURCE
}
TenantContext
租户上下文,存储当前租户的相关信息:
public class TenantContext {
/**
* 租户 ID
*/
private Long tenantId;
/**
* 隔离级别
*/
private TenantIsolationLevel isolationLevel;
/**
* 数据源信息
*/
private TenantDataSource dataSource;
}
TenantContextHolder
租户上下文 Holder,基于 ThreadLocal 实现线程级别的租户上下文管理:
public class TenantContextHolder {
private static final TransmittableThreadLocal<TenantContext> CONTEXT_HOLDER = new TransmittableThreadLocal<>();
/**
* 设置上下文
*/
public static void setContext(TenantContext context);
/**
* 获取上下文
*/
public static TenantContext getContext();
/**
* 获取租户 ID
*/
public static Long getTenantId();
/**
* 清除上下文
*/
public static void clearContext();
}
DefaultTenantLineHandler
默认租户行级隔离处理器,实现 MyBatis Plus 的 TenantLineHandler 接口:
public class DefaultTenantLineHandler implements TenantLineHandler {
@Override
public Expression getTenantId() {
Long tenantId = TenantContextHolder.getTenantId();
if (tenantId != null) {
return new LongValue(tenantId);
}
return null;
}
@Override
public String getTenantIdColumn() {
return tenantProperties.getTenantIdColumn();
}
@Override
public boolean ignoreTable(String tableName) {
// 超级租户忽略租户隔离
Long tenantId = TenantContextHolder.getTenantId();
if (tenantId != null && tenantId.equals(tenantProperties.getSuperTenantId())) {
return true;
}
// 数据源级隔离时忽略行级处理
if (TenantIsolationLevel.DATASOURCE.equals(TenantContextHolder.getIsolationLevel())) {
return true;
}
// 检查忽略表配置
return CollUtil.contains(tenantProperties.getIgnoreTables(), tableName);
}
}
核心依赖
依赖 | 描述 |
---|---|
top.continew.starter:continew-starter-extension-tenant-core | 多租户 - 核心模块 |
com.baomidou:mybatis-plus-jsqlparser | MyBatis Plus jsqlparser 适配(包含原扩展模块内容) |
com.baomidou:dynamic-datasource-spring-boot3-starter | Dynamic Datasource(基于 Spring Boot 的快速集成多数据源的启动器) |