本系列文章皆在从细节着手,由浅入深的分析Mybatis
框架内部的处理逻辑,带你从一个全新的角度来认识Mybatis
的工作原理。
思考,输出,沉淀。用通俗的语言陈述技术,让自己和他人都有所收获。
作者:毅航😜
前言
在前几章:
中我们利用大量的篇幅详细介绍了Mybatis
中Mapper.xml
配置的sql
语句与接口
中方法绑定的具体细节,并分析了Mybatis
中根据接口
构建实例
对象的背后逻辑。事实上,前几章的核心
内容通过如下这张图就能概括。
在几章的介绍中,我们一直都在围绕使用Mybatis
的四行代码进行分析。但这就结束了吗?当然不是!这四行
代码是你“简单”使用Mybatis
的极限,却不是Mybatis
的极限。
使用
Mybatis
的四行代码
// 加载配置文件
InputStream is = Resources.getResourceAsStream("mybatis.xml");
// 创建sessionFactory对象
sessionFactory = new SqlSessionFactoryBuilder().build(is);
// 获取sqlSession对象信息
SqlSession session = factoy.openSqlSession();
// 构建映射器的代理对象
UserMapper mapper = session.getMapper(UserMapper.class);
// .....调用相关方法信息
事实上,这四行
代码是Mybatis
提供给开发者方便其快速上手Mybatis
的。通过这样的编码,开发者就能实现接口
方法和sql
语句的绑定,进而获取到一个实现该接口的实例
对象。进一步,用户调用接口
中的方法将就能完成sql
的执行。
那Mybatis
内部又是如何来执行这些sql
语句的呢?别着急,这些内容都将在后续的文章都将会进行介绍。笔者将像剥洋葱一样,一层层的剥开潜藏在Mybatis
身上的全部秘密
。
此时你可能会疑惑Mybatis
中代码那么多,如果要分析Mybatis
中sql
执行的相关逻辑,应该从哪入手呢?
接下来不妨按着笔者的思路来逐一分析,看看笔者是如何来寻找Mybatis
中sql
执行的入口
位置的,相信看到最后你一定会有一种“拨云见日”的感觉。
重新认识SqlSession
在Mybatis流程分析(六): Mybatis中方法和sql语句的桥梁——MapperProxy我们曾提到:当调用Mybaits
内部经过动态带来返回的实例
对象中的方式时,其本质是从Configurtaion
对象中获取缓存的MappedStatement
对象,提取出其中的sql
信息,然后将sql
执行逻辑委托于SqlSession
来进行执行。 具体来看,这部分代码逻辑如下:
public class MapperMethod {
// .... 省略其他无关代码
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
}
// ....省略其他相似逻辑的代码
return result;
}
private void executeWithResultHandler(SqlSession sqlSession,
Object[] args) {
// 相当于对sql内容进行封装
MappedStatement ms = sqlSession.getConfiguration().
Object param = method.convertArgsToSqlCommandParam(args);
// 通过sqlSession中的select方法进行执行
sqlSession.select(command.getName(), param, method.extractResultHandler(args));
}
}
至于Mybatis
内部是如何调用到MapperMethod
中的executeWithResultHandler
方法的,笔者在此便不再重复论述了。不了解其中调用逻辑的可以回看Mybatis流程分析(六): Mybatis中方法和sql语句的桥梁——MapperProxy中的相关内容。
简单来看,MapprProxy
中的invoke
方法会将执行sql
的功能委托给MapperMethodInvoker
。进一步,MapperMethodInvoker
内部又会将处理逻辑委托给MapperMethod
中execute()方法进行处理。
事实上,Mybatis
中通过代理
返回的实例
对象,其增强逻辑本质就是交给MapperProxy
来进行完成的。具体来看,增强逻辑
本质就是在的MapperProxy
中的invoke
方法来完成的。
进一步,当调用Mybatis
所返回的实例对象的内部方法
时,其本质就是执行与方法绑定的sql
语句。换言之,只要顺着MapperProxy
中的invoke
方法的调用链寻找,就一定能找到Mybatis
中执行sql
的相关逻辑。
更进一步,相信MyBatis
中的 SqlSession
应该有一个更深刻的认识。其功能不仅仅只局限于会话管理
和对象生成
,其内部更是提供了许多方法来执行 sql
的操作,这其中包括 select
方法。
至此,我们先前提出的问题其实已经有了答案。换言之,若要探究Mybatis
中sql
执行的相关逻辑,则SqlSession
中的select
方法就是我们分析这一问题
的入口
。
藏在select
方法中的"秘密"
我们曾在Mybatis流程分析(二):配置处理,构建SqlSession工厂中提及过,在Mybatis
中SqlSession
为一个接口。而我们使用Mybatis
时,其内部
会为我们构建一个DefaultSqlSession
对象来进行会话的管理。所以,如果要分析Sqlsession
中select
方法的实现逻辑,我们重点关注其在DefaultSqlSession
中的实现就可以了。
DefaultSqlSession
#select
public void select(String statement, Object parameter,
RowBounds rowBounds, ResultHandler handler) {
根据namespce+methodId获取对应的MappedStatement
MappedStatement ms = configuration.getMappedStatement(statement);
调用执行器中Executor中的query方法
executor.query(ms, wrapCollection(parameter), rowBounds, handler);
// .... 省略其他无关逻辑
}
可以看到,DefaultSqlSession
中的select
方法内部会做两件事:
statmentId
信息从Configuration
中获取对应的MappedStatment
对象;MappedStatment
对象作为参数传递给 executor.query
。细心的读者可能会注意到上述代码中所示的select
方法其返回值为一个void
,如果我们接口中方法的返回值
类型为为一个实体对象
,那这样不就出错吗?
但实际的结果却和预期相反,在使用Mybatis
时并不会出现这样的bug
。至于其中的原因,笔者在此先不给出相关解答,这个问题的答案我们留在分析ResultHandler
的时候给予回答。
(注:DefaultSqlSession
中存在select
方法的多个重载
版本,但其最终都会调用到上述这个版本的select
方法)
总结
本文从MapperProxy
中的invoke
方法入手,逐一梳理invoke
方法的调用逻辑,由浅入深的分析了Mybaits
内部执行sql
的语句的入口
位置。具体来看,Mybatis
中执行sql
语句会委托给SqlSession
中的select
。进一步,sql
语句的执行又将委托给Executor
中的query
方法。
换言之,Mybatis
中sql
语句的相关执行都是通过Executor
来完成。事实上,MyBatis
中的 Executor
是一个核心组件,负责执行sql
语句与数据库的交互。它扮演着连接数据库、执行sql
语句、处理结果集等重要角色。而执行器——Executor
也将在下一章进行讨论,敬请期待。