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的解析和运行原理的更多相关文章

  1. Mybatis的解析和运行原理

    Mybatis的解析和运行原理 Mybatis的运行过程大致分为两大步:第一步,读取配置文件缓存到Configuration对象,用以创建 SqlSessionFactory:第二步,SqlSessi ...

  2. 互联网轻量级框架SSM-查缺补漏第七天(MyBatis的解析和运行原理)

    第七章MyBatis的解析和运行原理 SqlSessionFactory是MyBatis的核心类之一,其最重要的功能就是提供创建MyBatis的核心借口SqlSession,所以要先创建SqlSess ...

  3. Java并发编程原理与实战十二:深入理解volatile原理与使用

    volatile:称之为轻量级锁,被volatile修饰的变量,在线程之间是可见的. 可见:一个线程修改了这个变量的值,在另一个线程中能够读取到这个修改后的值. synchronized除了线程之间互 ...

  4. Java并发编程原理与实战三十七:线程池的原理与使用

    一.简介 线程池在我们的高并发环境下,实际应用是非常多的!!适用频率非常高! 有过使用过Executors框架的朋友,可能不太知道底层的实现,这里就是讲Executors是由ThreadPoolExe ...

  5. 试解析Tomcat运行原理(一)--- socket通讯

    关于这篇文章也确实筹划了很久,今天决定开篇写第一篇,说起tomcat首先很容易联想到IIS,因为我最开始使用的就是.net技术,我第一次使用asp写学生成绩管理系统后,很茫然如何让别人都能看到或者说使 ...

  6. 试解析Tomcat运行原理(一)--- socket通讯(转)

    关于这篇文章也确实筹划了很久,今天决定开篇写第一篇,说起tomcat首先很容易联想到IIS,因为我最开始使用的就是.net技术,我第一次使用asp写学生成绩管理系统后,很茫然如何让别人都能看到或者说使 ...

  7. .NET/ASP.NET MVC Controller 控制器(深入解析控制器运行原理)

    阅读目录: 1.开篇介绍 2.ASP.NETMVC Controller 控制器的入口(Controller的执行流程) 3.ASP.NETMVC Controller 控制器的入口(Controll ...

  8. 二、ASP.NET MVC Controller 控制器(一:深入解析控制器运行原理)

    阅读目录: 1.开篇介绍 2.ASP.NETMVC Controller 控制器的入口(Controller的执行流程) 3.ASP.NETMVC Controller 控制器的入口(Controll ...

  9. NET/ASP.NET MVC Controller 控制器(一:深入解析控制器运行原理)

    阅读目录: 1.开篇介绍 2.ASP.NETMVC Controller 控制器的入口(Controller的执行流程) 3.ASP.NETMVC Controller 控制器的入口(Controll ...

随机推荐

  1. HTTP的消息结构?

    参考:http://www.runoob.com/http/http-messages.html (1)请求数据包结构: 第一部分:请求行(数据包的第一行内容)[GET/HTTP/1.1] 请求行包含 ...

  2. mysql的主从复制原理与实现

    关于mysql的主从复制,之前一直在听说这个话题,一直没有实现,昨天学习了下,原来是这么回事: 既然是主从复制,那么肯定有主有从,也就说一个主数据库(一般为写库),一个从数据库(读库).主数据库更新了 ...

  3. [rsync]rsync设定及错误处理

    server端设置      修改/etc/default/rsync RSYNC_ENABLE=true RSYNC_OPTS='--address=10.192.0.5' RSYNC_NICE=' ...

  4. bzoj 4070 [Apio2015]雅加达的摩天楼 Dijkstra+建图

    [Apio2015]雅加达的摩天楼 Time Limit: 10 Sec  Memory Limit: 256 MBSubmit: 644  Solved: 238[Submit][Status][D ...

  5. oracle 国外网站【转载】

    [转自]:http://www.2cto.com/database/201406/306615.html 1. http://www.oratechinfo.co.uk/ http://www.ora ...

  6. Tomcat7项目迁移到Tomcat8中文乱码问题

    我打算开始使用Tomcat8了,先解决中文乱码问题,在解决其它的问题! 个人推荐:修改server.xml方式 对于SpringMVC报的错误我稍后在补充问题 1.问题描述 Tomcat 7下项目切换 ...

  7. CSS知识之 background-size 用法详细介绍

    background-size是CSS3新增的比较实用的属性,使用它可以随心所欲地控制背景图的显示大小,而在css2中背景图的大小是不可控制的. 基本语法: 用于设置背景图片的大小,有2个可选值,第1 ...

  8. 图论&数学:矩阵树定理

    运用矩阵树定理进行生成树计数 给定一个n个点m条边的无向图,问生成树有多少种可能 直接套用矩阵树定理计算即可 矩阵树定理的描述如下: 首先读入无向图的邻接矩阵,u-v G[u][v]++ G[v][u ...

  9. Linux局域网内文件传送

    先安装ssh服务 sudo apt-get install ssh 普通传输文件,可以使用scp命令 1.将本地文件复制到目标机器: scp  文件名 用户名@目标机器IP:目标机器路径 回车后输入密 ...

  10. 元类编程-- metaclass

    #类也是对象,type创建类的类 def create_class(name): if name == "user": class User: def __str__(self): ...