对于工作了一两年的同学,如果想深入源码提升竞争力,Mybatis 是最好的起点。Mybatis 代码量不大,设计简洁优雅,按照平时用到的功能,各个击破,可能几个小时就搞明白啦!
今天这篇文章,主要介绍 3 板斧,5 分钟搞懂 Mybatis 源码的核心流程,可以作为一个入门路线,分享给大家。
Mybatis 的三板斧
- SQL 组装(拼接动态SQL)
- SQL 执行(提交到数据库)
- SQL 结果处理(组装成 Java 对象)
Mybatis 作为一个 ORM 框架,其实就是在完成上述三个核心步骤。我们只需捏住 SQL 这个线头,挑动三板斧,整个过程就清晰了。
接下来,我们开始实战。
准备一个动态SQL程序
<mapper namespace="com.xcodemap.mybatis.mapper.UserMapper">
<select id="findUser" resultType="com.xcodemap.mybatis.mapper.User">
SELECT * FROM User
WHERE 1=1
<if test="age != null">
AND age = #{age}
</if>
<if test="name != null">
AND name = #{name}
</if>
</select>
</mapper>
try (SqlSession session = sqlSessionFactory.openSession()) {
UserMapper mapper = session.getMapper(UserMapper.class);
Map<String, Object> params = new HashMap<>();
params.put("age", 101);
params.put("name", "xcodemap101");
List<User> users = mapper.findUser(params);
System.out.println(users);
}
准备一个上下文序列图
要想 5 分钟搞懂核心流程,我们今天需要用到一个源码工具 XCodeMap,这个工具会录制程序的运行时行为,然后产生一个上下文序列图。
Debug with XCodeMap,运行结束,会看到如下的画面,左边是序列图,右边是上下文。
准备好之后,接下来,我们进入 Mybatis 的第一板斧,就是找到 SQL 拼装的关键函数。
这个时候注意了,如果我们一开始,就去逐个点开各个函数,层层往下看,序列图的节点数会呈指数级增长,大脑内存很快就爆了。
Mybatis 源码从入门到放弃,Game Over 了!
所以,敲黑板了!
当我们遇到陌生代码,不知道类名字,更不知道函数名字,但我们知道这些代码要完成的数据处理功能,此时我们可以直接根据数据值去搜索关键代码。具体到 Mybatis 的第一板斧,我们可以根据 SQL 去搜索关键代码。
值搜索带 #{} 的 SQL
我们先使用 XCodeMap 值搜索 “SELECT *”,可以看到带“#{}” 和 带“?”的语句,我们先选择带 “#{}”,毕竟这是我们在 XML 文件中配置的形式。
搜索完成之后,画面就仅剩 3 个函数了,分别是:
- DynamicContext::getSql
- SqlSourceBuilder::parse
- GenericTokenParser:parse
在右侧上下文观察各个函数的输入输出,可以很快发现,DynamicContext 负责返回带 *#{} *的SQL,而GenericTokenParser 负责把 *#{} *替换成 *? *。
目前知道了一些知识点,但还没解决 Mybatis 第一板斧的问题:
- SQL 是怎么拼接来的?
看上图,第一个返回 SQL 的地方是 DynamicContext,根据名字猜测,动态 SQL 应该跟 DynamicContext 有关。
这是一个线索,此时,我们需要做的是,把所有跟 DynamicContext 相关的函数调用给找出来:
- 作为调用者,调用了哪些函数?
- 作为参数,被哪些函数调用?
XCodeMap 可以通过对象追踪能力,一键完成这个目标。
追踪 DynamicContext 对象
右键 DynamicContext 对象,选择 “trace object”,就可以在左边的序列图上,把所有跟这个对象相关的节点标记出来。
图上可以清晰地标记出来,DynamicContext 主要是和 MixedSqlNode,IfSqlNode,StaticTextSqlNode 在进行交互。
这三类 Node 都有调用一个 apply 方法,因为他们都实现了 SqlNode
public interface SqlNode {
boolean apply(DynamicContext context);
}
这个时候,我们需要搞清楚这三个 Node 的基本职责,只需竖着看序列图,针对每个类,挨个点开成员函数,查看右侧的输入输出。
我们很快就会发现:
- MixedSqlNode 就是一个包装器,就遍历子节点,没有实质内容
- IfSqlNode 先 evaluate 一个表达式,如果为真,就继续往下执行子节点
- StaticTextSqlNode 调用 appenSql 组装最后的文本
整个过程就是,从rootSqlNode开始,深度优先遍历整棵树,把符合条件的 StaticTextSqlNode 的文本直接拼接起来就可。
如果这个时候,我们想进一步了解 rootSqlNode 是如何生成的,采取类似办法,进一步追踪即可。
到这里,Mybatis 的第一板斧就完成了,接下来是第二板斧:
- SQL 是怎么被提交到数据库执行的?
同样的问题,当我们对代码不熟悉,不知道关键函数的名字时,通过数据值搜索来定位。具体到 Mybatis 第二板斧,值搜索带 ? 的 SQL,这是数据库可以最终理解的 SQL。
值搜索带 ?的 SQL
搜索之后,画面上的节点略多。但没关系,我们迅速浏览一下,找到我们最熟悉的节点,很快,我们就会发现熟悉的 prepareStatement。
没错,这里就是大家学校期间都学过的 JDBC 调用范式,我们可以简单复习一下。
pstmt = conn.prepareStatement(sql);
pstmt.setString(1, "xcodemap"); // 设置第一个参数
pstmt.setInt(2, 101); // 设置第二个参数
rs = pstmt.execute();
while(rs.next()) {
}
如果这个时候,我们追踪一下 ClientPreparedStatement 这个对象,就可以发现熟悉的配方:
- setInt (设置参数 age)
- setString (设置参数 name)
- execute (执行数据库操作)
这里多说一句,利用熟悉的知识,去链接不熟悉的知识,是一个非常重要的学习技巧,甚至可以说是人脑认知世界的根本方式,有些大佬喜欢把这个过程叫做编织,AnyWay,如果你之前不了解这个概念,那么恭喜你,今天可以比别人多扫一个盲区。
回到 Mybatis,本质上它就是封装 JDBC 的 Preparement 调用范式,提供更加方便的操作体验而已。
此时,我们已经找到了关键的 execute 函数,好比我们在地图上已经锁定了目的地,此时需要做的是,找出出发地到目的地的路径。
利用 XCodeMap 的栈回溯能力,可以一键完成这个目标。
回溯 execute 堆栈
回溯之后,惊喜发生了,Mybatis 的 SQL 执行流程,一览无遗。
你可以在流程中,看到 SimpleExecutor → PrepareStatementHandler → DefaultResultSetHandler 等重要类缓缓呈现出来。
顺着这个核心流程,我们只需往上一步,也就是 PreparedStatementHandler 的 query 方法,可以非常直观地看到它调用了 DefaultResultSetHandler 的 handleResultSets,而这个方法返回了我们需要的 List,也就是说,就是这个方法,完成了 Java 对象的组装。
组装 Java 对象
这是最后一板斧了,胜利的曙光就在眼前。
在这最后一刻,我们不要忘记今天学到的内容,就是关注数据值,避免陷入指数级的代码调用中,否则就这 handleResultSets 也够你喝一壶的。
对于有返回值的函数,这时候,我们继续运用 XCodeMap 的对象追踪能力,追踪这个返回值的来龙去脉。
在图上,把一些函数折叠一下,整个思路就清晰了。
组装 Java 对象其实也是可以分成三个基本步骤:
- 构造对象,对应图上的 createResultObject 和 newMetaObject
- 获取 java属性到 db 列名的映射关系(图上没有直接显示,但你可以通过show stack 找到)
- 调用 setBeanProperty 进行赋值(点进去可以看到 TypeHandler)
总结
回顾一下我们今天的动作:
- 值搜索 原始 SQL —— 找到 GenericTokenParser,其完成 #{} 到 ?的转换
- 追踪对象 DynamicContext —— 找到动态 SQL 的组装过程
- 值搜索 最终 SQL —— 找到熟悉的 PrepareStatement 类
- 追踪类 ClientPrepareStatement —— 找到熟悉的 JDBC 调用范式
- 栈回溯 execute —— 找到核心流程,包括 SimpleExecutor,PrepareStatementHandler
- 追踪对象 User —— 找到 TypeHandler,了解属性的赋值过程
这里的 6 个动作,其实都是 XCodeMap 三板斧的灵活运用:
- 值搜索(搜索程序运行过程中产生的数据,比如 SQL 字符串)
- 对象追踪(追踪某个对象的来龙去脉,搞清楚对象的角色和演变)
- 栈回溯(溯源主干,找到支线)
用好 XCodeMap,避免陷入分支抽象的干扰,避免频繁 Debug,避免大脑内存爆炸,快速定位 Mybatis 的核心函数和基本逻辑,从而做到 5 分钟读懂 Mybatis 源码。
XCodeMap 或许是一个可以改变你竞争现状的工具,强烈推荐大家去试用。
参考: