MyBatis源码浅析

10 篇文章 0 订阅
9 篇文章 0 订阅
本文为博主原创,允许转载,但请声明原文地址: http://www.coselding.cn/article/2016/06/09/MyBatis源码浅析/

MyBatis源码浅析
我们来从使用过程的角度来看源码~

注:本人已经熟练掌握JDBC编程的各个细节,并且有Hibernate开发经验,对Hibernate的一些细节较为熟悉,所以过程中不免会因此忽略一些细节,有的话再给我指出即可。。。

Mybatis版本为3.3.1

1、 首先通过一个xml的配置文件的Reader对象来实例化一个SqlSessionFactory,过程如下:
SqlSessionFactoryBuilder().build(reader)
点进去build方法,发现里面一大堆的重载方法,最终结果是把传入的代表主配置文件的对象转换成XMLConfigBuilder,其构造方法主要包括三个参数,如下:
 
reader:xml主配置文件的Reader对象
environment:主配置文件environments节点下可以配置多个environment,这个参数用来指定要加载哪个environment,不指定话他会加载默认的配置,这里还没深究
properties:properties配置文件加载到内存后的对象

接下来呢,他会把刚才的xml文件解析成Document(显而易见),把Document和Properties对象存在XPathParser,作为XMLConfigBuilder的构造参数,XMLConfigBuilder里面保存了个标记位parsed,用来标记XMLConfigBuilder是否解析过了(很显而易见的设计方式),其中的parse方法把构造XMLConfigBuilder的那些对象封装成一个Configuration对象,看看Configuration对象内部,也不太需要看得太仔细,内部内容就是对配置文件的配置参数的集合,并提供了相应的set和get方法,之后的框架需要的配置参数就从他这边获得。

刚才讲的都是主配置文件,在主配置文件中可以找到<mappers/>节点,通过该节点可以找到各个实体的映射配置文件,和主配置文件一样的道理,读取这个映射配置文件,里面配置了对这个实体的各种操作的sql语句(select、insert、delete、update四种节点),每个把他提取出来,封装成MapperStatement,映射文件还配置了<resultMap/>节点,这些节点封装成ResultMap对象。

简要说一下这两个类中的字段:
MapperStatement:id、主键相关、sql命令类型(增删改查等类型)、结果集、缓存、参数映射、结果集映射、超时时间等等
ResultMap:id、要映射的JavaBean类、<resultMap/>四种子节点对应的List、数据库列集合、自动映射符号位等等
ParameterMap:id、参数映射对应的JavaBean类、JavaBean类中的属性和数据库列的映射列表

总结:把配置文件加载成Configuration、MapperStatement、ResultMap对象服务于后续的框架使用。
通过这些对象就可以得到配置文件的所有信息。

2、 返回SqlSessionFactoryBuilder类,最下面使用上面生成的Configuration对象创建了SqlSessionFactory接口的默认实现DefaultSqlSessionFactory,来看看DefaultSqlSessionFactory这个类内部干嘛的,如下:
好多个openSession的相关重载,但是都是统一调用了openSessionFromDataSource和openSessionFromConnection,下面看看这两个方法:
openSessionFromDataSource(从名字显而易见,从数据源获取Session对象):
 
(1) Transaction对象:事务对象,TransactionFactory开启事务返回的事务对象,封装了数据库连接对象,事务的提交、回滚等方法
(2) Environment:刚才说了,那个environment参数指定了主配置文件加载哪个数据源配置,在这里他就把加载的配置封装成一个对象了,里面包含了数据源对象和TransactionFactory
 
(3) TransactionFactory:事务工厂,封装了数据库事务的相关方法,事务隔离级别等,这个在使用JDBC的Connection对象的时候也有的功能,这里进行了封装,和Environment对象绑定,其中的autoCommit参数就是JDBC设置自动提交的参数(事务)
(4) Executor:执行器,SQL执行的核心类;
通过Configuration对象和事务对象创建执行器,并在拦截器链中的每个拦截器都注册一遍,才是真正完成了执行器对象的创建;

(5) DefaultSqlSession:和Hibernate类似的SqlSession对象,通过Configuration、执行器和autoCommit创建SqlSession对象;
 
executor对象包含了事务对象,连接对象
(6) ExecutorType:枚举,三个值:SIMPLE(简单执行器,用完释放),REUSE(这个执行器保存在List中,之后可以再拿来重复利用),BATCH(批处理);
这里呢,是根据这个类型去实例化相应的执行器(都是BaseExecutor的子类)
 
 openSessionFromConnection(从数据库连接对象创建SqlSession):和从数据源创建类似,只是少了从数据源中申请连接对象的过程。

总结:里面的类封装了好多层,也有好多设计模式,抽象类,实现延迟,装饰模式,继承多态等各种高大上哈哈哈,源码看多了就习惯了
总体过程呢,就是通过主配置文件得到的Configuration对象得到的连接池对象申请连接,并开启事务,得到一个SqlSession对象,这个对象封装了刚才申请的资源,通过这些连接、事务等资源对象来对数据库发送SQL命令(当然,SQL是在映射配置文件中的),总体过程就是申请好连接和事务,然后返回一个封装好的总体接口。。。

3、 SqlSession对象的使用:(主要两种用法)
(1) getMapper:通过传入一个实体的操作接口的class(这个接口我们只需要定义,不需要实现),返回这个接口的一个实现,接口中的方法是和映射配置文件的SQL操作id一一对应的;
 
点进去看啊,发现是直接调用Configuration的方法(这个对象水真深啊,之后有空返回来再好好研究它),再一直点进去,结果也是意料之中的,动态代理,他帮我们实现了我们定义的这个实体操作接口的实现类,把映射配置文件中的对应id的SQL操作封装进去了呗,原理很简单(合成Java源码动态编译),不去仔细看了。。。
要注意的一点是,他在中间多做了一个小动作,就是针对这个传入的class接口对象和动态合成的实现类用一个Map映射,其实就是缓存,万一这个class多次传入调用,每次都要动态编译,影响性能对吧。。。

这种调用方式是通过SqlSession得到一个实体操作类,之后调用这个实体就能直接操纵数据库了。
(2) 另一种:直接使用SqlSession的select、insert、delete、update方法来完成数据库操纵,其中里面有大量重载啊,select归结起来最后都调用了同一个select方法,而另外的insert、delete、update到最后都是调用同一个update方法,下面来仔细看看这两个方法:
Select:
 
第一步:通过id从Configuration对象查找MapperStatement对象(封装了映射配置文件中的一个SQL操作的节点信息),通过这个对象唯一确定要执行的SQL语句;
  还有个parameter:这个Object对象一开始不是很懂啊,但是他调用了一个wrapCollection方法,瞬间豁然开朗,这个Object其实是可能是一个值,也可能是一个数组或一个集合等等,那从他的名称就知道了,这个对象其实就是SQL语句中的占位符所要替换的那些参数,没理解的话联想一下PreparedStatement;

接下来就调用了executor的query方法啦,传入了四个参数,说明一下:
MappedStatement:要对应的SQL执行操作嘛,上面讲过了;
Object:wrapCollection的返回值,是把原来的Object解析完成之后的了;
RowBounds:查询操作行数范围,默认0~Integer的最大值,应该是和SQL的limit限定有关的;
ResultHandler:结果集处理器,明显是查询操作后的结果集在这里面处理,并返回相应的JavaBean或者JavaBeanList对象;

点进去看看executor到底怎么执行的:(此处是接口,我们看SimpleExecutor的过程为例)
SimpleExecutor只是实现了BaseExecutor没实现的方法(延迟到子类实现,但是调用是在父类,策略设计模式),所以还得点进去看BaseExecutor(不过这个样我们看的过程就是所有执行器的统一过程了)
 
创建一级查询缓存key,这个必须唯一,createCacheKey方法里面一堆的运算,反正就是要通过这个几个对象创建一个唯一的key对象,不深究了。。。
创建好key调用另一个query方法真正执行:
  
  过程简单讲就是先查一级缓存,找到的话直接返回,找不到再调用queryFromDatabase向数据库发送请求,接下来queryFromDatabase:
 
先在localCache申请(我跟你预订了一个位置,给我留着),然后调用子类才实现的doQuery方法,最后再把真正的查询结果放到预定的缓存位置。
然后就是看子类的doQuery具体实现了,SimpleExecutor为例:
 
先通过Configuration得到一个StatementHandler对象,在通过这个对象得到Statement对象,然后执行相应的执行方法,现在进去StatementHandler看看具体的操作过程:
 
在Configuration中创建一个包装类RoutingStatementHandler,然后在拦截器链interceptorChain执行一遍(万一拦截器要拦截。。。),进RoutingStatementHandler:
 
根据映射配置文件配置的SQL类型,分别创建Statement、PreparedStatement或CallableStatement(存储过程)的Handler,作为这个包装类的委托,可以分别到这三个类中去看个究竟,但是意义不大了,就是把很常见的JDBC操作进行了一步步的封装,这个封装的目的得根据整个架构的设计来考量,这里仅仅粗略学习一下,暂不深究。。。
里面有大量的异常处理,资源管理(连接对象、缓存等等)

但是,JDBC操作没什么好看的,有一点需要看看,ResultSetHandler是如何把查询结果集封装成JavaBean的?(显而易见是反射或者内省,不过,还是进去看看),见下面的结果集处理。

Update:增删改都调用这个方法,同样的,点进去BaseExecutor的update方法:
 
清空了下缓存,直接调用子类的doUpdate,看SimpleExecutor的:
 
呵呵,和Select的过程是一样的,只是这里传入的ResultSerHandler为null,因为不需要。。。

4、 结果集处理:
按照参考的博客说的,Mybatis对Java的反射进行了进一步的封装,把那些没法处理的异常进行了封装和处理,简化了Mybatis对反射的使用。
结果集处理主要涉及的类有:DefaultResultSetHandler,DefaultResultHandler,ResultSetWrapper,ResultMap,还有一系列数据库元数据,Mybatis全局对象等。
ResultSetWrapper:对ResultSet的包装类,包装了数据表的元数据(列名、列类型、Class名、映射等);
ResultMap:前面说了,映射配置文件读取到的<resultMap/>节点的对应Java对象;
DefaultResultSetHandler:对结果集的处理,大致过程就是循环结果集的每行,分别调用DefaultResultHandler去执行,在添加到List中;
DefaultResultHandler:将数据库查询得到的行封装到JavaBean或Map数据结构中,以下简略说明执行过程:
从DefaultResultSetHandler 的handleResultSets方法逐步深入去找,
 
分两种:一种普通类型,一种JavaBean中的一个属性是另一个JavaBean,resultMap嵌套嘛,仅分析第一种:
 
超出rowBounds范围的行忽略;
While遍历每行,先获取ResultMap映射对象;
getRowValue:应该就是把行通过映射转换成JavaBean,返回Object,点进去看看
  
createResultObject创建了JavaBean对象,进去看看,发现又有一层,再点进去:
 
这里resultMap.getType应该就是得到配置文件<resultMap/>的type属性了,即JavaBean的Class对象,就是根据这个创建JavaBean类对象,到这里先停,这个方法应该就是创建这个JavaBean对象并根据ResultMap的配置进行该对象的一些初始化,我们返回,先不深究。。。
 
退到这,Mybatis用JavaBean对象创建了一个代理对象,很好理解,这个是为了一对多相关关系的延迟加载的功能。(代理对象实现过程就不深究了,无外乎动态代理,或者自定义一些优化)
还是回到这里:
 
用resultObjcet创建一个MetaObject,看看MetaObject:
 
反射工厂,就是这里了,看来将ResultSet中的行反射到JavaBean就是在这里了。
再回过头来看getRowValue方法中的:


rsw:ResultSetWrapper,ResultSet的包装类
resultMap:映射配置文件中的对应的映射对象
metaObject:这个包含了要填充的JavaBean对象
lazyLoader:需要延迟加载的ResultMap部分
通过这些参数,可以很清楚的知道,这个方法,就是根据resultMap映射,把rsw中的数据库查询到的数据通过反射注入到metaObject的JavaBean对象中,延迟加载的对象先不执行。

主要过程大概这样,具体细节先不深究了,水很深。。。你如果点进去细细看每个细节,就会发现各种封装,各种设计模式,现在先做个大概吧,以后真的需要深究的时候,再好好品味。。。

总结:
(1) Mybatis没有完全实现对象关系映射,没有像Hibernate完整实现ORM框架,但是他轻量级地实现了对数据库操纵的简化,在某些方面甚至效率高于Hibernate;
(2) Mybatis和一般的ORM框架一样实现了基本的功能:一二级缓存、SQL与代码分离、延迟加载、连接池、简单的事务管理、Statement对象维护和重用、批处理、存储过程等基本功能;
(3) 如果你非常熟悉JDBC编程,其实理解这个框架还是很容易的,最基本的理解透彻,以他为基础的延伸都很好掌握;
(4) 如果有Hibernate使用和开发经验,也有助于掌握Mybatis,轻而易举。。。
(5) 再总结一下整个过程:首先从连接池获取一个连接对象,然后开启事务,创建一个事务对象,通过id查询映射配置文件中的SQL操作节点,通过该节点信息和传入的占位符替代参数合成相应的Statement对象,查询缓存(缓存有直接返回,没有才会执行SQL),发送SQL得到结果,通过resultMap节点映射信息和数据库的元信息将结果集封装到JavaBean或JavaBeanList,返回结果,提交事务,将连接对象返回给连接池。
主要过程就是这样,中间很复杂的处理细节还是去看源码就懂啦~

参考文档:
http://blog.csdn.net/fansunion/article/details/8254855 这个有好多篇,一直点下一篇即可看,粗略了解一下Mybatis
 http://www.mybatis.org/mybatis-3/zh/ 官方文档
 https://github.com/Coselding/mybatis-3 github仓库


本文为博主原创,允许转载,但请声明原文地址:http://www.coselding.cn/article/2016/06/09/MyBatis源码浅析/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值