跳转到内容

插件链与 SQL 改写

MyBatis-Plus 用一个外层 MyBatis Interceptor 统一承载多个 InnerInterceptor,把分页、租户、数据权限、乐观锁和防全表更新拆成可组合组件。

先给答案:MyBatis-Plus 是在 MyBatis 执行边界上做有序改写

Section titled “先给答案:MyBatis-Plus 是在 MyBatis 执行边界上做有序改写”

外层聚合器接收一次 MyBatis 执行,再按顺序调用分页、租户、逻辑删除等内层拦截器。每个插件都可能读取或修改 SQL、参数和分页上下文,因此顺序会改变最终语义:先改写表范围还是先生成分页 SQL,结果可能不同。

排查时不要停在 Mapper 方法名。应依次确认实体元数据、原始 SQL、拦截器链顺序、最终 BoundSql、数据库实际执行计划。插件能减少重复代码,却不能替代对复杂 SQL、索引和事务边界的判断。

MyBatis Invocation
└─► MybatisPlusInterceptor
├─► beforeQuery / willDoQuery / afterQuery
├─► beforeUpdate
├─► beforeGetBoundSql
└─► beforePrepare
└─► inner interceptors in registration order

MybatisPlusInterceptor#intercept 根据目标方法分派 query/update/boundSql,再按注册顺序调用 inner interceptor:见 MybatisPlusInterceptor.java:41-105、MybatisPlusInterceptor.java:117-155。

每个 inner interceptor 只关注一个横切问题,InnerInterceptor 提供默认生命周期钩子;这使插件可以只实现 beforeUpdate 或 beforePrepare:见 InnerInterceptor.java:38-120、PluginUtils.java:30-100、InterceptorIgnoreHelper.java:30-150。

插件适配 MyBatis 的入口仍是标准 Plugin#wrap 和 Interceptor#plugin:见 MybatisPlusInterceptor.java:160-190、MybatisPlusInterceptor.java:196-225、MybatisConfiguration.java:115-145。

替代方案:每个功能都注册一个 MyBatis Interceptor。 为什么不行:多个插件对 invocation、BoundSql 和忽略注解的处理会分散,顺序和配置难以管理。 证据:MybatisPlusInterceptor 只向 MyBatis 注册一次,内部维护 List<InnerInterceptor> 并统一分派。

场景 现象 原因 规避
插件顺序错误 count 或租户条件异常 前一个插件改变了后一个插件的输入 明确注册顺序并测试组合
忽略注解未生效 某 Mapper 仍被改写 ignore 判断依赖 statement id 和注解解析 检查注解位置与 mapper 方法
同时修改 BoundSql 参数索引错位 SQL 与 parameter mappings 不一致 使用 PluginUtils.mpBoundSql 同步修改

一个稳定的插件系统通常需要“单一外层适配器 + 多个内部策略 + 明确生命周期钩子”,这样组合顺序和可观测性更清晰。

面试锚点

  • MybatisPlusInterceptor 为什么是聚合器?
  • InnerInterceptor 的执行顺序如何影响结果?
  • 改 SQL 时为什么必须同步参数映射?