概要:
- 前言
- 整体架构
- 学习重点
- 学习路径
- 总结
前言
在MyBatis 3.5.1这个版中有968个类、2770个字段、8422个方法、42504行代码、186428个指令码。其源码体量只有Spring 的1/5,也是Hibernate的1/5。但在功能上这三个框架并无高低之分,但从源码轻量级来讲,MyBatis无疑是最成功的。
极简主义风格
这其中原因在于作者 Clinton Begin 极简主义风格,以及恰到好处的设计,不去盲目追求可扩展性。比如:在MyBatis代码中很少有注释,但你并不会因此看不懂,而Spring 源码中随处可见大量的注释,反而还看不懂,这就是极简主义的好处。
不增加新概念
此外MyBatis也是尽可能的不增加新概念。比如:它的插件机制和Spring AOP最终目的都是一样的。MyBatis的整个实现不到350行代码,涉及概念有拦截器、执行器两种。而Spring AOP 用一整个模块实现,代码有5700行。涉及概念有通知、连接点、切入点、切面、引入、目标、代理、织入等。这里我不评判这些概念有无必要,但它增加了用户理解难度,这点是确定的。
|
项目 |
版本 |
类 |
字段 |
方法 |
代码行(不含注释) |
指令码 |
|
MyBatis |
3.5.1 |
968个 |
2770个 |
8422个 |
42504行 |
186428条 |
|
Spring |
5.1.9 |
6135个 |
13266个 |
42652个 |
156679行 |
658330条 |
|
Hibernate |
5.4.22 |
5228个 |
13985个 |
45299个 |
152534行 |
624865条 |
综上所述,如果你想完整的吃透一个框架源码,MyBatis绝对是最省力气的。更重要的是它优秀的设计也非值得我们去学习。那我们应该如何开始呢?首先得了解它的宏观架构。
整体架构
MyBatis可分为三层,接口层、数据处理层、扩展与支撑层。
- 接口层
指用于操作数据库的增删改查接口,如SqlSession以及用户自定义的Mapper接口,都属于接口层组件。通常我们对MyBatis源码学习是从接口层开始的,但这并不是一个好方法,至于原因会在学习路径中讲到。
- 数据处理层
SQL执行流程实现,内容包括动态SQL语句构建、参数映射处理、结果映射集处理。他是整个MyBatis源码的核心,也是我们学习的重点和难点。
- 扩展与支撑层
指对主流程功能上的扩展与支撑。
- 扩展:对主流程,在易用性、性能上进行扩展。如SqlSession与Mappr接口就是易用性扩展。缓存与懒加载则是执行性能的扩展。
- 支撑:主程所依赖的工具,如数据源、反射工具、日志管理、配置构建器、脚本执行器等。
学习重点
数据处理层是MyBatis核心与重点,我们学MyBatis源码其实就是学它,搞定它MyBatis源码就掌握了80%,当然它的难度也是最大的。为方便理解,我把它分成了执行流程与映射配置两部分。
- 执行流程:体现SQL执行过程中的每个步骤。
- 映射配置:SQL执行前后的动态SQL解析、参数映射、结果集映射。
执行流程:
一个完整SQL执行流程,至少包含以下节点:
- **执行器:**处理一至多个SQL、以及、缓存、事务、批处理等。
- **Sql处理器 :**用于声明Statement、参数设置、执行、以及结果集处理。
- **参数处理器:**基于映射进行具体的参数赋值处理。
- **结果集处理器:**基于映射进行具体的结果集处理。
(注:图中绿色字体表示,表示交给下个组件实现,不是由自己实现)
每个节点都会完成SQL执行过程中特定任务,越到后面,其负责的事情就会越具体。
我们可以把 执行器理解成一个“指挥官”,它组织一至多次的SQL进攻,并把握进攻的节奏和方式,但自己不参与实际的战斗。
- **缓存:**佯攻,虚张声势并不会真正对数据库发起进攻。
- **批处理:**部队集结在一起,然后一起进攻
- 事务:决定何时结束战斗
- **SQL执行:**真正发起进攻(交给小队去执行)
Sql处理器理解成一个“队长”,它带队负责一次具体的SQL进攻。不仅自己要亲自上阵,还要分配任务给队员。
- **SQL声明:**申请作战武器
- **参数处理:**炮弹上膛,分配任务给队员
- **执行:**开火发射
- **结果集处理:**清理战场,分配任务给队员
最后参数处理器、结果集处理器、类型处理器都是负责具体局部任务的士兵。
将军、队长、士兵都有了,我们用户应该是什么呢?
用户当然是上帝啦,我们在上帝视角去配置、决定这场战争的走向。
映射配置
MyBatis源码中映射配置特指以下三件事情:
- **动态SQL:**基于配置的脚本,以及参数生成Sql字符串。
- **参数映射:**转换JAVA参数,并设置成SQL参数
- **结果集映射解析:**指定数据结果集,转换成JAVA对象。有1对1转换、1对多转换,还包括映射循环依赖等解决方案。它是MyBatis当中最难的知识点。
看到这里,你可能会奇怪,结果集映射,与结果集处理不是一回事吗?为啥要分开讲?确实是在干同一件事情。但关注点不一样,结果集处理关注流程,关注其在Sql流程当中的位置,上下承接了哪些组件?而勿略具体的处理细节。结果集映射就是具体的1对1、1对多的处理细节。
分开讲的目的,是帮助读者你能循序渐近,有层次的去掌握MyBatis,而不是一开始就陷入无休止的细节当中。参数映射也是同理的。
学习路径
在山脚下任何一条路都是通往山顶的,但不是随便哪条路都能登顶,
专业登山运动员一定懂的挑选最合适的路线。
关于MyBatis源码学习路径,供我们选择的有三条:
- 从上往下,按接口层、数据处理层、基础支撑层的顺序学习。
- 从下往上,反过来,先把底层依赖掌握,在去掌握上层应用。
- **从中间往两头,**即直接学习中间数据处理流程,然后在去学习其中牵扯出的技术。
思考:你会选择哪一种?你平时是按哪一种方式去学习?
我们应该选择第三种。因为源码的体量非常大,必须先抓核心。而第一种方式很难抓住核心,因为接口层是抽象的,其实例也要被代理、要被适配之后,才会进入核心。比如SqlSession在走SQL执行流程前,要经过系列的参数转换,或方法适配。同样也不适合用第三种,即使它是基础很重要,其原因是支撑层都是在为,数据处理层服务,如果脱离应用场景去学习,会加大理解难度。给人一种不能落地的感觉。
实践证明,第三种是最可靠的。直接去学习核心流程,在核心流程学习过程中,把相关依赖搞定。最后做为流程的延伸去讲解接口层。
最后确定学习路径如下:
- 执行流程
- 映射配置
- 扩展支撑
学习目标分解
不带着目标去看源码的人,他不是在学习,他只是在消磨时间。
学MyBatis源码之前一定要分解目标,并适时检查自己是否完成目标,这样才能持续学习下去。我们经常说源码学了就忘记,原因就是没有学习目标去检查,导致学了半桶水也不知道。这种不完善的知识体系是最容易忘记的。以下就是我们要给自设定的学习目标:
- 第一目标:Sql执行流程
MyBatis源码学习的第一个目标就是要搞定四个组件所组成的Sql执行流程。这个四个组件分别是,执行器(Execute)、Sql处理器(StatementHandler)、参数处理器(ParameterHandler)、结果集处理器(ResultSetHandler)。这个目标又可分解成4个小目标
- 理解四个组件执行上的顺序
- 每个组件的作用及意义
- 掌握Execute 的三个实现逻辑
- 掌握StatementHandler的三个实现逻辑
这个目标不是最难的,但是最要的。如果有一天你把学的MyBatis源码忘的差不多了,也希望你这个流程不要忘,这是MySql源码的主干和肪脉络。顺着它就可以把MyBatis知识在拾回来。
说明:关于参数处理、结果集处理其主要逻辑细节在于映射处理,可以放到映射篇章学习。
- 第二目标:映射配置
第二个要搞定的目标,就是SQL执行流程当中,所需要的三大映射:分别是动态SQL映射、参数映射、以及结果集映射。不仅要搞清楚映射配置的加载与解析,更重要的弄清楚映射逻辑。
- **加载解析:**指用户配置的XML解析成JAVA配置对象。比如.... 块,最终要解析成SqlSource。或... 要解析成ResultMap对象。
- **映射逻辑:**比如SqlSource如何基于参数生成可执行SQL,ResultMap如何将结果集,解析成JAVA对象等。
这里面涉及内容非常多,同样可以划分成一个个小目标:
- 理解Sql映射声明(MappedStatement)的作用,以及它与三大映射的关系。
- 掌握动态SQL(sqlSource)解析过程,包括其脚本表达示的执行逻辑
- 掌握MyBatis所封装的映射工具包的使用
- 掌握参数转换过程,以及映射的封装过程
- 掌握结果集映射核心逻辑,包括关联映射,1对1、1对多的处理。还有更复杂的循环依赖处理
- 掌握全部配置(Confgiuration) 的核心定位,以及它的加载解析过程
结果集映射是整个MyBatis源码当中的最难的点,其关联映射会涉及子查询、一级缓存引用、循环依赖、延迟加载、懒加载、自动映射等知识点。个个都不是善茬,攻克不易。
- 第三目标:扩展支撑
前面两个目标如果搞定了,恭喜你,MyBatis实质上你已经拿下了,接下来的学习就会有一种水道渠成的轻松。所谓扩展指对主流程的延伸。如:通过执行器可以完成所有功能,但使用太繁锁,会话(SqlSession)正是为简化它的使用。Mapper接口的设定也是一样,是为进一步简化使用会话。除了针对易用性扩展,还有提高执行性能的扩展,比如:缓存、懒加载等。最后的插件机制则是暴露给用户自定义的扩展。接下来我们继续分解学习目标:
- 掌握一二级缓存的结构,及执行逻辑。
- 理解会话的意义和其采用的设计模式
- 掌握Mapper接口动态代理流程
- 掌握插件机制底层原理
- 掌握懒加载的流程
在当前内容学习过程当中, 你也会掌握大量的设计模式,如二级缓存的装饰器+责任链模式、会话所采用的门面模式、以及Mapper接口对应动态代理模式等。
总结
最后总结一下:
- MyBatis源码最佳学习路径是,先抓主流程、在深入映射细节、最后在回到主流程的扩展上来,始终都要以主流为肪络。
- 在学习过程中,主流程是重点、映射细节是难点、其中结果集映射是难点中的难点。我们要保证先掌握重点,在去攻克难点。
- 学习之前一定要先划定目标,否则容易放弃。之所以学了就忘,是因为知识体系不够完整,必须要完整的学习MyBatis。
虽然MyBatis三个框架中源码量最少,但毕竟还是有上千个类、4万多行代码。面对这个体量我们一定要化繁为简,抓住核心。否则很容易陷入无边的逻辑细节当中。
最后祝大家,早日能拿下MyBatis去洞房,生出一个叫“知识”的娃儿来。