啃下MyBatis源码系列目录
啃下MyBatis源码 - 为什么要看MyBatis源码及源码结构
啃下MyBatis源码 - org.apache.ibatis.logging包源码分析
啃下MyBatis源码 - org.apache.ibatis.datasource包源码分析
啃下MyBatis源码 - org.apache.ibatis.cache包源码分析
啃下MyBatis源码 - MyBatis核心流程三大阶段之初始化阶段
啃下MyBatis源码 - MyBatis核心流程三大阶段之代理阶段(binding模块分析)
啃下MyBatis源码 - MyBatis核心流程三大阶段之数据读写阶段
--------------------------------------------------------------------------------------------------------------------------
啃下MyBatis源码 - MyBatis核心流程三大阶段之数据读写阶段
1.MyBatis是怎样的封装jdbc操作的
2.sqlSession查询流程图和Executor内部调用流程图
--------------------------------------------------------------------------------------------------------------------------
1.MyBatis是怎样的封装jdbc操作的
我们先来回忆一下jdbc代码:
//1.加载驱动
Class.forName("com.mysql.jdbc.Driver");
//2.获取连接conn
Connection con=DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "123");
//3.创建查询接口
Statement sta= con.createStatement();
//4.执行SQL,返回结果集
ResultSet rs= sta.executeQuery("SELECT * FROM `user`");
//5.对结果集数据进行操作
User user = new User();
user.setUserName(String.valueOf(rs.getObject(1)));
其中第一步加载驱动在MyBatis的初始化阶段就已经完成了,数据读写阶段就是处理sqlSession.executeQuery的阶段,对应JDBC第二步获取连接开始,到返回结果集封装对象结束。那MyBatis究竟是怎样封装JDBC操作的呢?我们先从sqlSession的默认实现DefaultSqlSession开始入手:
可以看到该类包含一个核心组件Executor(执行器),查询相关操作最终都借助该组件实现,那么我们来看一下Executor的关系类图:
BaseExecutor:基础抽象类,实现了executor接口的大部分方法,主要提供了缓存管理和事务管理的能力,使用了模板模式,doUpdate,doQuery,doQueryCursor 等方法的具体实现交给不同的子类进行实现
CachingExecutor:直接实现Executor接口,使用装饰器模式提供二级缓存能力。先从二级缓存查,缓存没有命中再从数据库查,最后将结果添加到缓存中。如果在xml文件中配置了cache节点,则会创建CachingExecutor。
BatchExecutor:BaseExecutor具体子类实现,在doUpdate方法中,提供批量执行多条SQL语句的能力;
SimpleExecutor:BaseExecutor具体子类实现且为默认配置,在doQuery方法中使用PrepareStatement对象访问数据库, 每次访问都要创建新的 PrepareStatement对象;
ReuseExecutor:BaseExecutor具体子类实现,与SimpleExecutor不同的是,在doQuery方法中,使用预编译PrepareStatement对象访问数据库,访问时,会重用缓存中的statement对象,而不是每次都创建新的PrepareStatement。
一下子丢出来这么多执行器有点蒙,没关系我们跟进一个查询流程走下来就清楚了。首先从DefaultSqlSession开始,我们调用的sqlSession.selectList方法:
可以看到只有BaseExecutor和CachingExecutor两个类重写了query方法,而CachingExecutor类前面也说过,在Configuration类初始化的时候如果在XML中配置了<cache>节点的话,则会用装饰器模式对基础执行器进行增强,使其拥有二级缓存能力,并且我们也可以看到在初始化Executor时是通过设定的类型来决定初始化哪一个执行器子类。
好的我们继续跟进BaseExecutor的query()方法:
可以看到首先通过MappedStatement拿到对应的SQL信息BoundSql,再封装一级缓存值CacheKey,具体的查询为先从一级缓存拿,如果一级缓存为空,就从数据库加载数据,具体从数据库查询的方法源码:
我们跟进默认实现SimpleExecutor的doQuery方法:
这段代码有两点值得我们注意,一个是prepareStatement(handler, ms.getStatementLog());这个方法,我们跟进去会发现:
终于找到了我们熟悉的JDBC代码,获取Connection,创建Statement查询接口;再一个是我们看到了四个新面孔,四种不同的处理器,一起来看下StatementHandler体系结构类图:
BaseStatementHandler: 所有子类的抽象父类,定义了初始化statement的操作顺序,由具体子类实例化不同的statement
CallableStatementHandler:调用存储过程
PreparedStatementHandler:使用预编译PrepareStatement对象访问数据库
RoutingStatementHandler:Excutor组件真正实例化的子类,使用静态代理模式,根据上下文决定创建哪个具体实体类
SimpleStatementHandler:直接使用statement对象访问数据库,无须参数化
RoutingStatementHandler类源码,很清晰的静态代理
接上文调用SimpleStatementHandler的query方法:
jdbc的execute()方法也找到了,最后借助DefaultResultSetHandler对数据库返回的结果集进行封装,返回用户指定的实体类型。handleResultSets()方法部分源码:
处理结果集的过程略复杂,这里只简单的梳理下MyBaits对于结果集封装的步骤:
-
创建multipleResults集合,保存最终返回的结果。
-
取出第一个结果集
-
获取对应的resultMap
-
根据resultMap转化结果集,转换成目标对象后添加到multipleResults集合;
-
resultset.close()关闭结果集,将multipleResults集合返回
2.sqlSession查询流程图和Executor内部调用流程图
sqlSession查询流程图:
Executor内部调用流程图:
至此MyBatis核心流程最后一个阶段:数据读写阶段完成。