多租户过滤的核心目标是SQL执行前动态注入tenant_id作为查询条件,确保数据隔离;需拦截StatementHandler.prepare()方法,结合SqlParser解析并安全重写SQL,统一处理SELECT/INSERT/UPDATE/DELETE,通过TenantContext动态获取租户ID,避免SQL注入与逻辑干扰。

理解多租户过滤的核心目标
在 MyBatis 插件开发中实现多租户字段自动拼接过滤,本质是在 SQL 执行前动态注入租户标识(如 tenant_id)作为查询条件,确保每个租户只能访问自己的数据。关键不是修改所有 SQL,而是对特定 Mapper 接口、方法或注解标记的语句生效,且不影响原逻辑和分页、排序等其他功能。
使用 Interceptor 拦截 StatementHandler
MyBatis 插件应拦截 StatementHandler 的 prepare() 方法,这是 SQL 已解析为 BoundSql、尚未执行的阶段,适合安全地重写 SQL 和参数:
- 通过
MetaObject获取当前BoundSql的sql和parameterObject - 判断当前 SQL 是否属于需多租户隔离的语句(例如:检查命名空间 + 方法名,或扫描 Mapper 接口上的自定义注解如
@TenantScoped) - 若需拦截,用
SqlParser(推荐 jsqlparser)解析原始 SQL,向 WHERE 子句追加AND tenant_id = ?,并扩展参数列表 - 构造新
BoundSql并替换原对象,确保后续流程(如参数绑定)正常工作
租户上下文与参数绑定策略
租户 ID 必须可动态获取,常见方式是结合 ThreadLocal 或 Spring 的 RequestScope:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- 定义
TenantContext工具类,提供getCurrentTenantId(),由网关/Filter/Interceptor 在请求入口设值(如从 Header、JWT 或数据库路由规则中提取) - 拼接 SQL 时,不硬编码值,而是占位符
?;同时将租户 ID 作为额外参数插入到parameterObject中(若原参数是 Map,则 put 入;若是 POJO,可用反射添加临时字段或改用 Map 封装) - 避免直接字符串拼接 SQL,防止 SQL 注入和类型错误;务必使用预编译参数机制
支持 INSERT/UPDATE/DELETE 的租户写保护
仅拦截 SELECT 不够——INSERT/UPDATE/DELETE 同样需校验或强制设置租户字段:
立即学习“Java免费学习笔记(深入)”;
- 对 INSERT:解析 SQL,若表含
tenant_id字段但未显式指定,自动补上tenant_id = ?到列名和 values 中 - 对 UPDATE/DELETE:强制追加
AND tenant_id = ?到 WHERE 条件(即使原 SQL 已有 WHERE),防止越权修改/删除其他租户数据 - 可通过配置白名单控制哪些表/操作启用写保护,避免影响系统表或共享配置表
规避常见陷阱
实际落地时容易踩坑:
- 不要拦截 Executor.update() —— 它发生在 SQL 执行后,无法修改已生成的 SQL
- 动态 SQL(
<if>,<where>)需确保租户条件始终生效,建议在最外层<where>内追加,或统一用<and>避免语法错误 - MyBatis-Plus 的自动填充(
MetaObjectHandler)与租户插件职责不同:前者填值,后者控查,二者可共存但需明确分工 - 单元测试必须覆盖无租户上下文场景(如定时任务),此时应抛异常或跳过拦截,而非静默忽略

















