MyBatis 通过自定义 TenantInterceptor 插件实现多租户隔离:拦截 Executor 的 query/update 方法,动态注入 tenant_id 条件;配合 ThreadLocal 管理租户上下文,在请求入口设置并清理;需规范 SQL 编写与插件注册顺序。

MyBatis 本身不内置多租户支持,但可通过自定义 Interceptor(插件) 在 SQL 执行前动态注入租户标识(如 tenant_id),实现数据行级隔离。核心思路是:拦截 Executor 的 query/update 方法,改写 SQL(添加 WHERE 条件或修改参数),确保每次查询/更新自动带上当前租户上下文。
租户上下文管理
需在线程级维护当前租户 ID,常用方式是 ThreadLocal + 工具类:
- 定义 TenantContext 类,提供
setTenantId(String)、getTenantId()、clear() - 在请求入口(如 Spring MVC 拦截器、Filter 或网关)解析租户标识(Header、子域名、Token 声明等),调用
TenantContext.setTenantId("t1") - 务必在请求结束时调用
TenantContext.clear(),避免线程复用导致租户信息污染
编写 TenantInterceptor 插件
实现 MyBatis Interceptor 接口,重点拦截 Executor.query 和 Executor.update 方法:
- 使用
@Intercepts注解指定目标签名,例如:@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}) - 在
intercept()中获取当前租户 ID;若为空,可跳过拦截(允许系统级操作)或抛异常 - 通过
MappedStatement.getBoundSql().getSql()获取原始 SQL;推荐使用SqlSource替换方案(更安全)——重写BoundSql,在 WHERE 子句后追加AND tenant_id = #{tenantId},并把 tenantId 加入参数映射 - 对 INSERT/UPDATE/DELETE 语句同样处理:INSERT 补
VALUES(..., #{tenantId}),UPDATE/DELETE 补WHERE ... AND tenant_id = #{tenantId}
SQL 映射适配与注意事项
插件生效的前提是 SQL 编写符合规范:
立即学习“Java免费学习笔记(深入)”;
- 所有涉及租户数据的表必须包含
tenant_id字段(类型建议为 VARCHAR 或 BIGINT),且该字段不允许为 NULL - Mapper XML 中避免写死
WHERE tenant_id = ?,让插件统一控制;若业务确需显式过滤(如跨租户管理),可用特殊标记(如注释/* NO_TENANT_FILTER */)绕过插件 - 使用
<where></where>标签而非拼接 WHERE,便于插件安全追加条件;避免WHERE 1=1这类写法干扰逻辑 - 注意分页插件(如 PageHelper)的执行顺序:TenantInterceptor 应在 PageHelper 之前注册,否则分页后的 SQL 可能漏掉租户条件
注册插件与验证
在 MyBatis 配置中声明插件:
- Spring Boot 中,在
@Configuration类里定义SqlSessionFactoryBean,调用setPlugins(new TenantInterceptor()) - XML 配置方式:在
<plugins></plugins>节点下添加<plugin interceptor="com.example.TenantInterceptor"></plugin> - 验证方法:开启 MyBatis 日志(log4j2 或 slf4j 输出 SQL),检查生成的 SQL 是否含
AND tenant_id = ?;同时用不同租户 ID 发起请求,确认查不到其他租户数据


















