明天你会感谢今天奋力拼搏的你。
ヾ(o◕∀◕)ノヾ
Mybatis源码因为相对简单比较适合想进行源码学习的初始材料,所以在此作为本源码系列的第一篇。本篇文章主要介绍Mybatis的核心工作流程,让初学者知道该从哪方面入手进行源码学习。
当前使用的版本:Mybatis 3.5.13
示例工程Gitee地址:点击跳转
1、从Mybatis源码地址中把Mybatis源代码Clone下来,在Tags中找到3.5.13的版本并Checkout作为本文使用的版本。
2、下载示例工程,如下图所示把Mybatis的源文件改为我们Checkout到本地的源码路径。(注意要选择到src\main\java文件夹下)。
示例工程介绍:
整个示例工程的代码非常简单就不做过多介绍,在示例工程中有一个测试类TestSqlSession,是我们研究Mybatis源码的入口。代码如下:
第一步,我们来看看Mybatis启动时都做了哪些操作?
通过如下两行我们可以看到,Mybatis的启动,其实就是加载全局配置文件丢给SqlSessionFactoryBuilder进行构件。
// 加载MyBatis配置文件
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
// 通过建造者模式创建复杂对象,Builder中会完成配置文件的加载和SqlSessionFactory的创建
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
其中运用到了工厂模式和建造者模式:
TIP:Mybatis与Spring的级联(mybatis-spring)中也是采用类似的加载方式,读取Spring配置文件中的Mybatis相关配置,构件为Configuration实例,注入到SqlSessionFactoryBuilder中完成Mybatis的加载工作。
第二步:深入查看SqlSessionFactoryBuilder是如何构造SqlSessionFactory的
如下图所示,核心的代码就是如下3行:
第三步:继续查看XMLConfigBuilder中的parse方法是如何解析配置文件到Configuration实例中
解析Mybatis配置的主方法如下图所示,对Mybatis的全局配置文件中所有节点都进行了解析。
再以解析properties节点为例,继续跟进,如下图所示,最终把节点中的所有数据都解析到configuration实例中。
第四步:映射文件的解析
上文第三步中,会调用mapperElement方法,此方法中就会解析mappers节点中配置的Mapper路径,找到映射文件放到XMLMapperBuilder中进行解析。
如下图所示,本文章的示例代码会执行下图方框中的内容,调用XMLMapperBuilder中的parse方法解析映射文件。
继续深入XMLMapperBuilder中的parse方法,找到解析Mapper XML的主要方法为configurationElement
以解析CURD相关元素(buildStatementFromContext)为例,继续往下查看代码,调用链如下。
中间调用链的代码在此就不一一展示了,直接跳到addMappedStatement函数中,看最终CURD的映射语句是如何加载的。
如上图所示,通过解析到的CRUD相关字段又会放到一个Builder中,通过Builder生成MappedStatement实例,最终放到Configuration实例的mappedStatement字段中。
要注意的一点,上图208行,存入Builder中的id是与命名空间拼接后生成的ID,而mappedStatement字段实际为一个Map,存入的key即为命名空间+标签ID。示例代码中通过SqlSession调用查询方法List<User> list = session.selectList("cyx.example.demo.mybatis.dao.UserMapper.selectAllUsers");传递的入参也为命名空间+标签ID。
通过上文可知,我们获得了DefaultSqlSessionFactory的实例,通过此实例调用openSession方法即可获得SqlSession。
SqlSession 接口也是外观模式(Facade Pattern)的典型应用,通过提供一个统一的接口,隐藏系统的复杂性,让客户端只需与高层接口交互。
// 获取SqlSession
SqlSession session = factory.openSession();
第一步,看看openSession方法中都干了啥
openSession方法的核心逻辑如下图所示。
在mybatis初始加载时已经把配置信息存入了configuration实例中,openSessionFromDataSource方法中其实就是把配置拿出来首先创建执行器,再把执行器注入到DefaultSqlSession中返回,即完成了SqlSession的创建。
第二步:看看执行器是如何创建的
如下图所示,执行器的创建也不复杂,根据执行类型ExecutorType生成不同的执行器,然后再进行缓存装饰和拦截器处理。
通过测试方法的第四行代码,我们深入追踪看看Mybatis的查询逻辑是怎样的:
List<User> list = session.selectList("cyx.example.demo.mybatis.dao.UserMapper.selectAllUsers");
第一步:查看selectList逻辑
从上文可知,SqlSession实例化的是DefaultSqlSession的实例,其selectList核心代码如下:
第二步:继续跟踪执行器是如何执行的
如下图,执行器是在上文SQLSession创建时生成的,可能分两种实现,CachingExecutor执行器就对应二级缓存的执行器。而BaseExecutor则是用的模板方法设计模式,其实现有默认的简单执行器、批处理执行器和重用执行器。
如果开启了二级缓存,则会进入CachingExecutor,我们首先来看下二级缓存是如何实现的
第三步:查看二级缓存实现逻辑
如下图所示,使用的也是很常规的缓存处理方式,如果缓存中有数据,则从缓存获取,没有则去查一遍数据库然后放到缓存中。
要注意的点:
第四步:查看BaseExecutor是如何执行查询
看起来代码这么多,其实核心逻辑还是判断localCache一级缓存中是否有数据,有就拿缓存中的数据,没有则执行查询逻辑。
第五步:继续追踪实际的查询数据库逻辑
上图中doQuery的逻辑在实际的执行器实现中,默认是SimpleExecutor,我们以SimpleExecutor为例继续往下看。
如上图所示,已经能看到JDBC的代码了,其中封装了语句处理器、参数处理器、结果集处理器,最后在语句处理器的query方法中进行数据库查询。
上文中的测试方法都是使用的SqlSession中的查询方法直接调用查询,而实际项目中肯定不会写死statement字段,改为如下以动态代理的方式进行调用就会显得更加优雅。
// 获取SqlSession
SqlSession session = factory.openSession();
// 一种方式是通过SqlSession提供的方法来操作数据库
// List<User> list = session.selectList("cyx.example.demo.mybatis.dao.UserMapper.selectAllUsers");
// 另一种方式,获取接口的动态代理对象直接调用
UserMapper userMapper = session.getMapper(UserMapper.class);
List<User> list = userMapper.selectAllUsers();
TIP:如果对动态代理不熟的可以查看我的另一篇文章:《Java动态代理源码分析》
第一步:查看getMapper方法的具体实现
直接往下追踪代码,可以看到如下图所示逻辑:
第二步:继续追踪MapperProxyFactory是如何newInstance的
从上图可以看出,最终的动态代理处理器是MapperProxy
第三步:查看MapperProxy的invoke方法
cachedInvoker方法主要是为了生成调用器,本示例中会生成普通方法调用器PlainMethodInvoker的实例。
继续查看普通方法调用器PlainMethodInvoker中的invoke方法
第四步:查看MapperMethod中的execute方法
如下图所示,很明显,最后动态代理中还是会根据CURD调用SqlSession中对应的方法执行数据库查询。
上文其实还有一些知识点没讲到,如:参数处理器、结果集处理器、拦截器,Mybatis与Spring的集成,后续有时间另开一篇文章再讲。
全部评论