《深入浅出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 ...
随机推荐
- oracle中varchar2字段存入blob字段及blob转成varchar2
CREATE OR REPLACE FUNCTION C2B (b IN CLOB default empty_clob()) RETURN BLOB -- typecasts BLOB to CLO ...
- bzoj3224: Tyvj 1728 普通平衡树(打个splay暖暖手)
(其实今天好热啊? 题目大意:插入,删除,k小,前驱后继,数的排名. splay和treap裸题...过几天补个treap的 splay: #include<iostream> #incl ...
- 使用openssl进行文件加密
#include <iostream> #include <string> #include <stdlib.h> using namespace std; int ...
- linux上修改系统默认语言设置
locale命令设置语言环境(临时修改) [keysystem@localhost ~]$ date Fri Feb :: CST [keysystem@localhost ~]$ locale LA ...
- Codeforces Round #546 (Div. 2) 题解
Codeforces Round #546 (Div. 2) 题目链接:https://codeforces.com/contest/1136 A. Nastya Is Reading a Book ...
- Makefile中的 =,:=,?=,+= 的差异
在Makefile中常常遇见这几种等操作,总结一下具体区别. = 是最基本的赋值 := 是用右值覆盖左值 ?= 判断,如果左值没有被赋值过就赋以右值,否则,不做赋值动作 += 在左值后面连接右值 ...
- 【题解】Matrix BZOJ 4128 矩阵求逆 离散对数 大步小步算法
传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=4128 大水题一道 使用大步小步算法,把数字的运算换成矩阵的运算就好了 矩阵求逆?这么基础的线 ...
- synchronize 和volatile 实现共享变量在多线程中的可见性
1.什么是线程可见性 可见性:一个线程对共享变量值的修改能够及时被其他线程看到. 共享变量:如果一个变量在多个线程工作内存中都存在副本,那么着给按量就是这几个线程的共享变量. 2.导致共享变量在线程间 ...
- Chrome控制台报错个人总结(不定时更新)
最近开始使用Chrome控制台检测代码错误,对于经常碰到的报错做一个汇总,免得每次遇到都要重新想一遍策略,错误原因,重复劳动,浪费时间. 由于不是每个错误都能碰到,以下仅列出个人写代码时经常碰到的报错 ...
- Asp.Net Web Forms/MVC/Console App中使用Autofac
本来简单介绍了Autofac在Asp.Net Web Forms中的应用,后来又添加了mvc.控制台应用程序中使用Autofac,详情请看源码. ASP.NET Web Forms使用Autofac, ...