《深入浅出MyBatis技术原理与实战》——6. MyBatis的解析和运行原理
MyBatis的运行分为两大部分,第一部分是读取配置文件缓存到Configuration对象,用以创建SqlSessionFactory,第二部分是SqlSession的执行过程。
6.1 涉及的技术难点简介
Mapper是一个接口,而接口是没有办法去执行的,那么它是怎么运行的呢?答案是动态代理,MyBaits会为Mapper产生代理类,为此先来学习下动态代理。一般而言,动态代理分为两种,一种是JDK反射机制提供的代理,另一种是CGLIB代理。
6.1.1 反射技术
6.1.2 JDK动态代理
由java.lang.reflect.*包提供支持的,我们需要完成这么几个步骤。
- 编写服务类和接口,这个是真正的服务提供者,在JDK代理中接口是必须的
- 编写代理类,提供绑定和方法。
JDK最大的缺点是需要提供接口,而MyBatis的Mapper就是一个接口,他采用的就是JDK动态代理。我们首先提供一个服务接口,如:

然后写一个实现类:

现在让我们写一个代理类,提供真实对象的绑定和代理方法。代理类的要求是实现InvocationHandler接口的代理方法,当一个对象被绑定后,执行其方法的时候就会进入到代理方法里,如:



下面这段代码:

让JDK产生一个代理对象。这个代理对象有3个参数,第一个参数target.getClass().getClassLoader()是类加载器,第二个参数target.getClass().getInterfaces()是接口(代理对象挂在哪个接口下),第三个参数this代表当前HelloServiceProxy类。
一旦绑定后,在进入代理对象方法调用的时候就会到HelloServiceProxy的代理方法上,代理方法有三个参数:第一个proxy是代理对象,第二个是当前调用的那个方法,第三个是方法的参数。比方说,现在HelloServiceImpl对象用bind方法绑定后,返回其占位,我们再调用proxy.sayHello("张三"),那么它就会进入到HelloServiceProxy的invoke()方法。而invoke参数中第一个便是代理对象proxy,方法便是sayHello(),参数是张三。
我们已经用HelloServiceProxy类的属性target保存了真实的服务对象,那么我们可以通过反射技术调度真实对象的方法:

现在让我们测试一下动态代理:


运行结果:

6.1.3 CGLIB动态处理
JDK提供的动态代理存在一个缺陷,就是必须要提供接口才可以使用,为了克服这个缺陷,我们可以使用开源框架——CGLIB。让我们看看如何使用CGLIB动态代理。
HelloService.java和HelloServiceImpl.java都不需要改变,但是我们要实现CGLIB的代理类:


这样便能够实现CGLIB的动态代理。在MyBatis中通常在延迟加载的时候才会用到CGLIB的动态代理。
6.2 构建SqlSessionFactory过程
第一步,通过org.apache.ibaits.builder.xml.XMLConfigBuilder解析配置的XML文件,读出配置参数,并将读取的数据存入这个org.apache.ibatis.session.Configuration类中。注意,MyBatis几乎所有的配置都是存在这里的。
第二步,使用Configuration对象去创建SqlSessionFactory实现类,我们一般都会使用org.apache.ibatis.session.defaults.DefaultSqlSessionFactory这个默认的实现类。
这种创建的方式就是一种Builder模式。对于复杂的对象而言,直接使用构造方法构建室友困难的,这会导致大量的逻辑放在构造方法中。这个时候使用一个参数总领全局,例如,Configuration类,然后分布构建。
6.2.1 构建Configuration

6.2.2 映射器的内部组成
一般而言,一个映射器是由3个部分组成:


这里只列举了主要的属性和方法。MappedStatement对象涉及的东西较多,一般不去修改它。SqlSource是一个接口,主要作用是根据参数和其他的规则组装SQL,一般也不需要修改。对于参数和SQL而言,主要的规则都反映在BoundSql类对象上,在插件中往往需要拿到它进而可以拿到当前运行的SQL和参数以及参数规则,做出适当的修改,来满足我们特殊的需求。
BoundSql会提供3个主要的属性:parameterMappings、parameterObject和sql。



6.2.3 构建SqlSessionFactory
6.3 SqlSession运行过程
6.3.1 映射器的动态代理
Mapper映射是通过动态代理来实现的,我们首先来看看代码清单:


这里可以看到动态代理对接口的绑定,作用就是生成动态代理对象,而代理的方法则被放到了MapperProxy中。MapperProxy的源码:

上面运用了invoke方法。一旦mapper是一个代理对象,那么它就会运行到invoke方法里面,invoke首先判断他是否是一个类,显然这里Mapper是一个接口不是类,所以判定失败。那么就会生成MapperMethod对象,它是通过cachedMapperMethod方法对其初始化的,人后执行execute方法,把sqlSession和当前运行的参数传递进去。让我们看看这个execute方法的源码:




MapperMethod采用命令模式运行,根据上下文跳转到许多方法中。看以看到里面的executeForMany方法,实际上它最后就是通过sqlSession对象去运行对象的SQL。现在便可以明白MyBatis为什么只用Mapper接口便能够运行SQL,因为映射器的XML文件的命名空间对应的便是这个接口的全路径,那么根据全路径和方法名便能够绑定起来,通过动态代理技术,让这个接口跑起来。然后采用命令模式,最后还是使用SqlSession接口的方法使得它能够执行查询,有了这层封装我们便可以使用接口编程。
6.3.2 SqlSession下的四大对象
我们已经知道了映射器其实就是一个动态代理对象,进入到了MapperMethod的execute方法。它经过简单的判断就进入了SqlSession的删除,更新,插入,选择等方法,那么这些方法如何执行呢?
通过类名和方法名字就可以匹配到我们配置的SQL,我们不需要去关心这些细节,我们关心的是设计框架。Mapper的执行过程是通过Executor、StatementHandler、ParameterHandler和ResultHandler来完成数据库操作和结果返回的。

6.3.2.1 执行器
执行器起到了至关重要的作用。它是一个真正地执行java和数据库交互的东西。在MyBatis中存在三种执行器。我们可以在MyBatis的配置文件中进行选择:

让我们看看MyBatis如何闯将Executor:

在创建对象后,他会执行下面这样一行代码:

这就是MyBatis的插件,这里它将我们构建一层层的动态代理对象。在调度真实的Executor方法之前执行配置插件的代码可以修改。现在我们可以看看执行器方法内部,以SIMPLE执行器SimpleExecutor的查询方法作为例子尽心讲解,如:


显然MyBatis根据Configuration来构建StatementHandler,然后使用prepareStatement方法,对SQL编译并对参数进行初始化。调用了StatementHandler的prepare()进行了预编译和基础设置,然后通过StatementHandler的parameterize()来设置参数并执行,resultHandler再组装查询结果返回给调用者来完成一次查询。这样我们的焦点又转移到了StatementHandler上。
6.3.2.2 数据库会话器
StatementHandler是用来专门处理数据库会话的,让我门先来看看MyBatis是如何创建StatementHandler的,再看Configuration.java生成会话器的地方,如:


很显然创建的真实对象是一个RoutingStatementHandler对象,它实现了接口StatementHandler。和Executor一样,用代理对象做一层层的封装。
RoutingStatement不是我们真实的服务对象,它是通过适配器模式找到对应的StatementHandler来执行的。在MyBatis中,StatementHandler和Executor一样分为三种:SimpleStatementHandler,PreparedStatementHandler,CallableStatementHandler。它所对应的是之前提到过的三种执行器。
在初始化RoutingStatementHandler对象的时候它会根据上下文环境决定创建哪个StatementHandler对象,下面是RoutingStatementHandler的源码:


数据库会话器定义了一个对象的适配器delegate。它是一个StatementHandler接口对象,构造方法根据配置来适配对应的StatementHandler对象。它的作用是给实现类对象的使用提供一个统一,简易的使用适配器。此为对象的适配器模式,可以让我们使用现有的类和方法对外提供服务,也可以根据实际的需求对外屏蔽一些方法,甚至是加入新的服务。现在已最常用的PreparedStatementHandler为例,看看MyBatis是怎么执行查询的。看它三个主要的方法,prepare,parameterize和query

然后会调用parameterize()方法去设置参数:

这个时候它是调用ParameterHandler去完成的,这里先我们先来看看StatementHandler的查询方法:

在Executor里,会先调用StatementHandler的prepare()方法预编译SQL语句,同时设置一些基本运行的参数。然后用parameterize()方法启用ParameterizeHandler设置参数,完成预编译,跟着就是执行查询,而update()也是这样的,最后如果需要查询,我们就用ResultHandler封装结果返回给调用者。
下面再讨论另外两个对象的使用,ParameterHandler和ResultSetHandler
6.3.2.3 参数处理器
在6.3.2.2节中看到了MyBatis是通过参数处理器(ParameterHandler)对预编译语句进行参数设置的。它的作用是完成对预编译参数的设置。它的定义如下:

其中,getParameterObject()方法的作用是返回参数对象,setParameter()方法的作用是设置预编译SQL语句的参数。
MyBatis为ParameterHandler提供了一个实现类DefaultParameterHandler,我们来看看setParameters的实现:


从parameterObject对象中取参数,然后使用typeHandler进行参数处理。而typeHandler也是在MyBatis初始化的时候,注册在Configuration里面的,我们需要的时候可以直接拿来用。这样就完成了参数的设置。
6.3.2.4 结果处理器
组装结果集返回。下面是结果处理器(ResultSetHandler)的接口定义:

其中,handleOutputParameter()方法是处理存储过程输出参数的,重点看一下handleResultSets()方法,它是包装结果集的。MyBatis同样为我们提供了一个DefaultResultSetHandler类,默认情况下都是通过这个类处理的。这个实现有些复杂,涉及到JAVASSIST或者CGLIB作为延迟加载,然后通过typeHandler和ObjectFactory进行组装结果再返回。
6.3.3 SqlSession总结

SqlSession是通过Executor创建StatementHandler来运行的,而StatementHandler要经过下面三步:
- prepared预编译SQL
- parameterize设置参数
- query/update执行SQL

《深入浅出MyBatis技术原理与实战》——6. MyBatis的解析和运行原理的更多相关文章
- Mybatis的解析和运行原理
Mybatis的解析和运行原理 Mybatis的运行过程大致分为两大步:第一步,读取配置文件缓存到Configuration对象,用以创建 SqlSessionFactory:第二步,SqlSessi ...
- 互联网轻量级框架SSM-查缺补漏第七天(MyBatis的解析和运行原理)
第七章MyBatis的解析和运行原理 SqlSessionFactory是MyBatis的核心类之一,其最重要的功能就是提供创建MyBatis的核心借口SqlSession,所以要先创建SqlSess ...
- Java并发编程原理与实战十二:深入理解volatile原理与使用
volatile:称之为轻量级锁,被volatile修饰的变量,在线程之间是可见的. 可见:一个线程修改了这个变量的值,在另一个线程中能够读取到这个修改后的值. synchronized除了线程之间互 ...
- Java并发编程原理与实战三十七:线程池的原理与使用
一.简介 线程池在我们的高并发环境下,实际应用是非常多的!!适用频率非常高! 有过使用过Executors框架的朋友,可能不太知道底层的实现,这里就是讲Executors是由ThreadPoolExe ...
- 试解析Tomcat运行原理(一)--- socket通讯
关于这篇文章也确实筹划了很久,今天决定开篇写第一篇,说起tomcat首先很容易联想到IIS,因为我最开始使用的就是.net技术,我第一次使用asp写学生成绩管理系统后,很茫然如何让别人都能看到或者说使 ...
- 试解析Tomcat运行原理(一)--- socket通讯(转)
关于这篇文章也确实筹划了很久,今天决定开篇写第一篇,说起tomcat首先很容易联想到IIS,因为我最开始使用的就是.net技术,我第一次使用asp写学生成绩管理系统后,很茫然如何让别人都能看到或者说使 ...
- .NET/ASP.NET MVC Controller 控制器(深入解析控制器运行原理)
阅读目录: 1.开篇介绍 2.ASP.NETMVC Controller 控制器的入口(Controller的执行流程) 3.ASP.NETMVC Controller 控制器的入口(Controll ...
- 二、ASP.NET MVC Controller 控制器(一:深入解析控制器运行原理)
阅读目录: 1.开篇介绍 2.ASP.NETMVC Controller 控制器的入口(Controller的执行流程) 3.ASP.NETMVC Controller 控制器的入口(Controll ...
- NET/ASP.NET MVC Controller 控制器(一:深入解析控制器运行原理)
阅读目录: 1.开篇介绍 2.ASP.NETMVC Controller 控制器的入口(Controller的执行流程) 3.ASP.NETMVC Controller 控制器的入口(Controll ...
随机推荐
- 洛谷 P2324 [SCOI2005]骑士精神 解题报告
P2324 [SCOI2005]骑士精神 题目描述 输入输出格式 输入格式: 第一行有一个正整数T(T<=10),表示一共有N组数据.接下来有T个5×5的矩阵,0表示白色骑士,1表示黑色骑士,* ...
- [POI2007] ZAP-Queries (莫比乌斯反演)
[POI2007] ZAP-Queries 题目描述 Byteasar the Cryptographer works on breaking the code of BSA (Byteotian S ...
- mysql绿色版安装,多实例安装
1.为什么要装多个mysql多实例? 关于这个的原因,我目前了解为建立一个主数据库,一个或者多个从库,实现一主多从或者主从复制的目的. 2.设么是mysql的多实例? MySQL多实例就是在一台机器上 ...
- ES6中函数的扩展
一.设置默认参数 ES6之前,给函数设置默认参数是这样做的: function fn(a) { if(typeof y === undefined){ a = a || 'hello'; } cons ...
- http-反向代理学习
主要是学习了反向代理. 结合公司的方向代理使用,然后与同事进行交流,知识还是需要通过交流才能印象深刻,以后多多交流.
- uva 1506 Largest Rectangle in a Histogram
Largest Rectangle in a Histogram http://acm.hdu.edu.cn/showproblem.php?pid=1506 Time Limit: 2000/100 ...
- 2015/9/20 Python基础(16):类和实例
面向对象编程编程的发展已经从简单控制流中按步的指令序列进入到更有组织的方式中,依靠代码块可以形成命名子程序和完成既定的功能.结构化的或过程性编程可以让我们把程序组织成逻辑快,以便重复或重用.创造程序的 ...
- [洛谷P1822] 魔法指纹
洛谷题目连接:魔法指纹 题目描述 对于任意一个至少两位的正整数n,按如下方式定义magic(n):将n按十进制顺序写下来,依次对相邻两个数写下差的绝对值.这样,得到了一个新数,去掉前导0,则定义为ma ...
- JAVA JDBC(存储过程和事务管理)
1.什么是存储过程 存储过程(Stored Procedure)是在大型数据库系统中,一组为了完成特定功能的SQL 语句集,存储在数据库中,经过第一次编译后再次调用不需要再次编译,用户通过指定存储过程 ...
- 【51NOD-0】1008 N的阶乘 mod P
[算法]简单数学 [题解]多项式展开:(a*b)%p=(a%p*b%p)%p #include<cstdio> #include<algorithm> #define rep( ...