场景设定:假设我们正在开发一个SaaS化办公系统,每个企业租户的数据就像平行宇宙中的星球——彼此独立却共享同一片数据库星系。如何让SQL飞船在查询时自动导航到对应星球?MyBatis-Plus的租户拦截器就是我们的"曲速引擎"!
一、💡 多租户设计的"字段级隔离"模式
在字段级方案中,所有业务表都携带tenant_id字段(如同数据护照),MP通过SQL拦截器自动为每个CRUD操作加盖"签证章"。
优势:
- 像变色龙般融入现有表结构
- 无需物理隔离的运维复杂度
- 拦截器如同隐形管家,业务代码零侵入
二、🔧 核心装备:TenantLineInnerInterceptor
这个拦截器就像SQL的智能修正带,在语句生成时悄悄补上租户条件:
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 添加租户拦截器(像给MP戴上AR眼镜)
interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(new TenantHandler() {
// 当前租户ID获取(可从ThreadLocal或SecurityContext获取)
@Override
public Expression getTenantId() {
return new StringValue("T001"); // 示例写死,实际动态获取
}
// 租户字段名(每个表的签证印章位置)
@Override
public String getTenantIdColumn() {
return "tenant_id";
}
// 是否需要进行租户过滤的表(白名单机制)
@Override
public boolean ignoreTable(String tableName) {
return !"sys_user".equalsIgnoreCase(tableName); // 示例仅过滤用户表
}
}));
return interceptor;
}
}
三、🚀 让实体类携带"租户基因"
为实体类植入tenant_id字段,就像给每个数据对象颁发身份证:
@Data
@TableName("sys_user")
public class User {
private Long id;
private String name;
@TableField(value = "tenant_id")
private String tenantId; // 租户标识基因片段
}
四、🌰 CRUD操作演示:隐形的数据国界
插入操作:
User user = new User();
user.setName("张三");
userService.save(user); // 无需设置tenant_id!
生成SQL:
INSERT INTO sys_user (name, tenant_id) VALUES ('张三', 'T001')
查询操作:
userService.lambdaQuery()
.like(User::getName, "张")
.list();
生成SQL:
SELECT * FROM sys_user
WHERE name LIKE '%张%'
AND tenant_id = 'T001' -- 自动追加的租户边界
五、🔍 原理解密:拦截器的"魔法时刻"
- SQL解析:拦截器通过AST(抽象语法树)分析SQL结构
- 条件注入:在WHERE条件中插入
tenant_id = ? - 参数绑定:将当前租户值绑定到预处理语句
- 忽略策略:通过
ignoreTable方法跳过系统表等特殊场景
整个过程如同在SQL生成流水线上安装了一个智能质检员,确保每个出厂的SQL都带有正确的租户标签。
六、⚠️ 注意事项:多租户开发的"生存法则"
- 租户ID获取:建议通过ThreadLocal或SecurityContext动态获取
- 数据初始化:手动插入数据时需主动填充tenant_id
- 复杂SQL:自定义SQL需使用
${ew.customSqlSegment}保留租户条件 - 超级管理员:可通过
@InterceptorIgnore(tenantLine = "true")跳过过滤 - 唯一性约束:需将tenant_id加入联合唯一索引
七、🚁 进阶技巧:在复杂查询中驾驭租户条件
当需要手动控制租户条件时,可使用Wrapper的租户方法:
// 临时切换租户上下文
try(TenantContext.switchTo("T002")){
userService.list(new LambdaQueryWrapper<User>()
.ne(User::getName, "管理员"));
}
// 忽略租户过滤(危险操作!)
userService.list(Wrappers.<User>lambdaQuery()
.ignoreTenantCondition());
总结:通过MyBatis-Plus的租户拦截器,我们实现了业务代码与租户逻辑的星舰分离。这种设计让系统就像拥有自动导航功能,开发者只需专注业务逻辑,数据的平行宇宙自然有序运转。这种优雅的解耦,正是现代SaaS系统架构的艺术所在!