MyBatis插件安全修改SQL需选StatementHandler.prepare拦截点,用JSqlParser解析AST精准改写,通过setAdditionalParameter注入参数,配合ThreadLocal隔离上下文。

MyBatis 插件中安全修改 SQL,核心在于“不破坏原有逻辑、不引入线程风险、不绕过参数绑定机制”。直接反射改 BoundSql.sql 字段看似简单,但容易出错;真正稳妥的做法是结合拦截点选择、SQL 解析与参数注入三者协同。
选对拦截点:StatementHandler.prepare 是最常用且可控的位置
修改 SQL 必须在语句真正交由 JDBC 执行前完成,StatementHandler.prepare(Connection, Integer) 是最合适的切入口。它确保:
- 原始 SQL 已经由
SqlSource解析完毕,BoundSql可用 - 参数对象(
parameterObject)已确定,可读取上下文信息(如当前租户 ID、用户权限) - 尚未生成 PreparedStatement,仍有空间重写 SQL 并重新绑定参数
用 JSQLParser 替代正则:避免误改非 SELECT 语句
直接用字符串替换或正则拼接 WHERE 条件极易出错——比如把 INSERT INTO user (name) VALUES ('xxx') 中的 name 当成字段名处理。推荐使用 JSqlParser:
- 解析 SQL 得到抽象语法树(AST),精准定位
SELECT语句的where子句或UPDATE/INSERT的列值部分 - 对多表 JOIN、子查询、UNION 等复杂结构也能保持结构完整性
- 支持方言适配(如 MySQL 的
LIMIT、Oracle 的ROWNUM),便于后续扩展分页或多租户逻辑
参数注入必须走 BoundSql.setAdditionalParameter()
修改后的 SQL 若含占位符(如 AND tenant_id = ?),不能靠字符串拼接传参,否则会跳过 MyBatis 参数处理器,导致类型转换失败或 SQL 注入漏洞。正确做法是:
立即学习“Java免费学习笔记(深入)”;
- 调用
boundSql.setAdditionalParameter("tenantId", currentTenantId) - 确保该 key 在 SQL 中以
#{tenantId}形式引用,由DefaultParameterHandler安全渲染 - 敏感字段(如手机号、身份证号)在日志中需脱敏,但参数传递本身仍走标准流程,不手动拼接值
注意代理链与线程上下文隔离
MyBatis 插件本质是责任链模式,多个插件共存时顺序影响结果。安全实践包括:
- 避免在
intercept()中修改共享状态(如静态 Map),改用ThreadLocal存储本次请求的租户/权限上下文 - 若需跨拦截器传递信息(如审计插件要读分页插件加的参数),可通过
MetaObject.forObject(boundSql)写入自定义属性,而非依赖外部变量 - 不要在插件里做耗时操作(如远程鉴权),应提前在业务层完成并存入
parameterObject或ThreadLocal


















