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 放行,手术成功。

相关推荐
盟接之桥1 小时前
线束数字化--先进先出为什么总是停留在纸面上?
大数据·网络·数据库·人工智能·制造·ai编程
小辰爱喝汤1 小时前
新闻管理系统|SpringBoot + Vue 毕业设计完整方案
spring boot·毕业设计·课程设计
FYKJ_20101 小时前
springboot助农产品销售商城05829-计算机课程设计、毕业设计
java·spring boot·python·mysql·spark·django
adinnet20262 小时前
预算管控:预算执行与偏差问数
大数据·数据库
Ticnix2 小时前
RAG 烂大街?烂大街的只是那条流水线——真正的分水岭在这五处
后端·python·agent
Wx-bishekaifayuan2 小时前
springboot社区扶贫救助管理系统13300-计算机课程设计、毕业设计
spring boot·后端·python·django·课程设计·express·旅游
DolphinDB2 小时前
数据库自带 Agent:10 分钟搭起 DolphinX-Web 数据入库与分析框架
后端·架构
弈栈录3 小时前
基于 Spring Boot 构建生产级 AI 应用平台
后端·面试·架构
小小张说故事3 小时前
LightGBM 入门指南:更快的梯度提升树,Python 实战
后端·python·机器学习
站大爷IP3 小时前
Python的默认参数把我坑惨了,原来写[]和写None的区别这么大
后端