MybatisPlusInterceptor实现无感修改SQL的底层原理(源码)

MybatisPlusInterceptor底层原理(源码)

比较枯燥,且调用链条庞杂,建议结合调试看,或者出bug时再调试

一、原理时序图

二、源码调用栈

三、 核心类关系图 (UML 简图)

聚合(List)
实现
继承
继承
组合(持有)
<<interface>>
InnerInterceptor
+beforeQuery()
<<interface>>
DataPermissionHandler
+getSqlSegment() : Expression
MybatisPlusInterceptor
-List<InnerInterceptor> interceptors
+intercept()
+addInnerInterceptor()
<<abstract>>
JsqlParserSupport
#parserSingle() : String
#processParser()
<<abstract>>
BaseMultiTableInnerInterceptor
#processSelectBody()
#processPlainSelect()
#builderExpression()
DataPermissionInterceptor
-DataPermissionHandler handler
+beforeQuery()
#processSelect()

四、 角色具体

1. InnerInterceptor (接口)
  • 角色岗位说明书
  • 职责:定义了所有 MP 插件(分页、多租户、数据权限)必须遵守的标准。
  • 关键方法
  • beforeQuery(): 查询前执行(改写 SQL 的主要场所)。
  • beforeUpdate(): 更新前执行。
2. MybatisPlusInterceptor (类)
  • 角色医院院长 / 总调度中心
  • 职责 :它是唯一直接对接 MyBatis 原生拦截机制的入口。它不干具体活,只负责维护一个 InnerInterceptor 列表(医生团队),并按顺序分发任务。
  • 关键方法
  • intercept(): MyBatis 的原生拦截点。
  • addInnerInterceptor(): 注册子插件(招聘医生)。
3. JsqlParserSupport (抽象父类)
  • 角色麻醉师 & 翻译官
  • 职责 :负责脏活累活。将原本看不懂的 SQL 字符串解析为结构化的 AST(抽象语法树),并在手术结束后把树还原为字符串。它还负责初步的路由(是 Select 还是 Update?)。
  • 关键方法
  • parserSingle(): 总入口。调用 JSQLParser 解析 SQL,触发处理流程,最后还原 SQL。
  • processParser(): 路由器。判断 SQL 类型(Select/Insert...),分发给对应的处理方法。
4. BaseMultiTableInnerInterceptor (抽象父类)
  • 角色资深主刀医生 (导航专家)
  • 职责 :它知道怎么拆解复杂的 SQL。它能深入到 SQL 的五脏六腑(FROM 子句、JOIN 子句、子查询、CTE),找到所有涉及的 Table
  • 关键方法
  • processSelectBody(): 递归处理 Select 主体。
  • processPlainSelect(): 拆解最基本的 SELECT ... FROM ... 结构。
  • builderExpression(): 手术核心 。遍历解析出的表列表,循环调用子类的 buildTableExpression(或直接调 Handler),并将生成的条件拼装回 AST。
5. DataPermissionInterceptor (具体实现类)
  • 角色数据权限专科医生
  • 职责 :它是 BaseMultiTableInnerInterceptor 的具体实现。它持有"业务顾问"(Handler),负责将 Handler 的策略应用到手术过程中。
  • 关键方法
  • beforeQuery(): 响应院长的号召,启动 parserSingle
  • processSelect(): 重写父类方法,启动 AST 遍历。
  • setRuleHandler(): 装载业务策略。
6. DataPermissionRuleHandler (接口/用户实现)
  • 角色业务顾问 / 智囊团
  • 职责纯业务逻辑。它不关心 SQL 怎么解剖,只关心"对于这张表,你是谁?你要看什么数据?"。
  • 关键方法
  • getSqlSegment(): 输入表名/别名,输出 SQL 条件片段(如 dept_id = 1)。

五、 协同工作全景剧本

现在,当一条 SQL SELECT * FROM sys_user u 发起时,各组件的协同流程如下:

第一阶段:接诊与分发
  1. MyBatis 准备执行 SQL。
  2. MybatisPlusInterceptor (院长) 拦截请求,遍历医生名单。
  3. 发现 DataPermissionInterceptor (专科医生) ,调用其 beforeQuery 方法。
第二阶段:麻醉与解析 (JsqlParserSupport)
  1. DataPermissionInterceptor 内部调用父类 JsqlParserSupport.parserSingle()
  2. JsqlParserSupport 调用工具类,将 SQL 字符串解析为 AST 对象树
  3. JsqlParserSupport 识别出这是 SELECT 语句,回调子类的 processSelect
第三阶段:手术导航 (BaseMultiTableInnerInterceptor)
  1. 逻辑进入 BaseMultiTableInnerInterceptor 的层级(因为继承关系)。
  2. processSelectBody -> processPlainSelect 开始工作。
  3. 它解析出主表 sys_user,别名 u
  4. 它调用 builderExpression,准备开始处理这张表。
第四阶段:专家会诊 (Handler)
  1. builderExpression 暂停,拿起电话打给 DataPermissionRuleHandler (顾问)
  2. 问:"sys_user 表要加什么条件?"
  3. Handler 运行用户代码,返回 u.dept_id = 1
第五阶段:缝合与出院
  1. builderExpression 拿到 u.dept_id = 1,将其通过 AND 连接到 AST 的 WHERE 节点上。
  2. 递归结束,控制权回到 JsqlParserSupport
  3. JsqlParserSupport 调用 toString(),将改写后的 AST 树变回 SQL 字符串。
  4. DataPermissionInterceptor 利用反射,将新 SQL 塞回 MyBatis 的 BoundSql
  5. MybatisPlusInterceptor 放行,手术成功。

相关推荐
yumgpkpm2 小时前
银行的数据智能平台和Cloudera CDP 7.3(CMP 7.3)的技术对接
数据库·人工智能·hive·hadoop·elasticsearch·数据挖掘·kafka
optimistic_chen2 小时前
【Redis 系列】常用数据结构---String类型
数据结构·数据库·redis·缓存·string
fanruitian2 小时前
springboot openai 调用functioncall
java·spring boot·spring·ai·springai
大猫子的技术日记2 小时前
Redis 快速上手实战教程:从零搭建高性能缓存系统
数据库·redis·缓存
莳花微语2 小时前
记录一次生产中mysql主备延迟问题处理
数据库·mysql
Hello.Reader2 小时前
Flink JDBC Driver把 Flink SQL Gateway 变成“数据库”,让 BI / 工具 / 应用直接用 JDBC 跑 Flink SQL
数据库·sql·flink
李宥小哥2 小时前
SQLite02-安装
数据库
javadaydayup2 小时前
MyBatis 映射值报错的罪魁祸首竟然是 Lombok 的 @Builder?
后端
一 乐2 小时前
景区管理|基于springboot + vue景区管理系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·后端·学习