MyBatis系列六 MyBatis的解析和运行原理

  如果你只限于MyBatis的普通使用,不打算使用插件,那么请你跳过本章。因为在前5章我们对MyBatis的应用已经有了较为详细的阐述,翻阅前面的内容,熟悉它们,你就

可以成为一名能够正确使用MyBatis的开发者。本章是有一定难度的,因为它讲述的是 MyBatis底层的设计和实现原理,原理就意味着晦涩难懂,对Java初学者来说,这甚至难

以理解,本章更加适合对Java有一定经验且参与过设计的开发者阅读,不过初学者通过仔 细阅读和反复推敲还是能够掌握的。

  本章所谈的原理只涉及基本的框架和核心代码,不会面面俱到,比如我不会告诉你 MyBatis是如何解析XML文件和其他配置文件从而得到内容的,JDBC如何使用,因为这 些都是Java基础,不是本章关心的问题。我们还是集中在MyBatis框架的设计和核心代码 的实现上,一些无关的细节将会被适当忽略。

  MyBatis的运行分为两大部分,第一部分是读取配置文件缓存到Configuration对象, 用以创建SqlSessionFactory,第二部分是SqlSession的执行过程。相对而言,SqlSessionFactory 的创建比较容易理解,而SqlSession的执行过程远远不是那么简单了,它将包括许多复杂 的技术,我们需要先讨论反射技术和动态代理技术,这是揭示MyBatis底层架构的基础, 本章每节都是上下关联的,需要按本章的顺序阅读,否则你会迷失在这个过程中。

  当我们掌握了 MyBatis的运行原理,我们就可以知道MyBatis是怎么运行的,这为我 们学习插件技术打下了基础。本文也会带领大家对一些关键源码进行阅读与分析,源码中 的一些技巧、设计和开发模式会使开发者受益匪浅。

一、涉及的技术难点简介

  来到原理章,我们有必要对一些常用的、基础的技术难点进行简介,否则读者可能难以理解本章的内容。

  正如我们第2章描述的那样,Mapper仅仅是一个接口,而不是一个包含逻辑的实现类。 我们知道一个接口是没有办法去执行的,那么它是怎么运行的呢?这不是违反了教科书所 说的接口不能运行的道理吗?相信不少的读者会对此产生极大的疑惑。

  答案就是动态代理,我们不妨看看Mapper到底是一个什么东西,如图6.1所示。

 

 

 

  很显然Mapper产生了代理类,这个代理类是由MyBatis为我们创建的,为此我们不妨 先来学习一下动态代理,这有利于后续的学习。

  首先,什么是代理模式?所谓的代理模式就是在原有的服务上多加一个占位,通过这 个占位去控制服务的访问。这句话不太容易理解,举例而言,假设你是一个公司的工程师, 能提供一些技术服务,公司的客服是一个美女,她不懂技术。而我是一个客户,需要你们 公司提供技术服务。显然,我只会找到你们公司的客服,和客服沟通,而不是找你沟通。 客服会根据公司的规章制度和业务规则来决定找不找你服务。那么这个时候客服就等同于 你的一个代理,她通过和我的交流来控制对你的访问,当然她也可以提供一些你们公司对 外的服务。而我只能通过她的代理访问你。对我而言,根本不需要认识你,只需要认识客 服就可以了。事实上,站在我的角度,我会认为客服就代表你们公司,而不管真正为我服务的你是怎么样的。

  其次,为什么要使用代理模式?通过代理,一方面可以控制如何访问真正的服务对象, 提供额外服务。另外一方面有机会通过重写一些类来满足特定的需要,正如客服也可以根 据公司的业务规则,提供一些服务,这个时候就不需要劳你大驾了。下面给出动态代理示 意图,如图6.2所示。

 

 

 

  一般而言,动态代理分为两种,一种是JDK反射机制提供的代理,另一种是CGLIB 代理。在JDK提供的代理,我们必须要提供接口,而CGLIB则不需要提供接口,在MyBatis 里面两种动态代理技术都已经使用了。但是在此之前我们需要学习的技术就是反射,让我 们开始基础技术的学习。

1、反射技术

  在Java中,反射技术已经大行其道,并且通过不断优化,Java的可配置性等性能得到 了巨大的提高。让我们来写一个服务打印“hello +姓名”,如代码清单6.1所示。

代码清单6-1: ReflectService.java反射示例

import java.lang.reflect.InvocationTargetException; 
import java.lang.reflect.Method; public class ReflectService {   /**    *服务方法    * @param name --姓名   */ public void sayHello(String name) (   System.err.printIn("hello" + name); } /** *测试入口 * @param args */ public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException (   //通过反射创建Ref lectService对象     Object service = Class.forName(ReflectService.class.getName()). newlnstance();   //获取服务方法 sayHello     Method method=service.getClass().getMethod ("sayHello", String. class);   //反射调用方法     method.invoke(service, "zhangsan”);   } }

  这段代码通过反射技术去创建ReflectService对象,获取方法后通过反射调用。

  反射调用的最大好处是配置性大大提高,就如同Spring IOC容器一样,我们可以给很 多配置设置参数,使得Java应用程序能够顺利运行起来,大大提高了 Java的灵活性和可配 置性,降低模块之间的耦合。

2、JDK动态代理

  JDK的动态代理,是由JDK的java.lang.reflect.*包提供支持的,我们需要完成这么几个步骤。

    • 编写服务类和接口,这个是真正的服务提供者,在JDK代理中接口是必须的。
    • 编写代理类,提供绑定和代理方法。

  JDK的代理最大的缺点是需要提供接口,而MyBatis的Mapper就是一个接口,它釆用 的就是JDK的动态代理。我们先给一个服务接口,如代码清单6-2所示。

 

 

 然后,写一个实现类,如代码清单6-3所示。

 

 

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

 

 

 

  上面这段代码让JDK产生一个代理对象。这个代理对象有三个参数:第一个参数target. getClass () . getClassLoader ()是类加载器, getlnterfaces()是接口(代理对象挂在哪个接口下),第三个参数this代表当前 HelloServiceProxy类,换句话说是使用HelloServiceProxy的代理方法作为对象的代理执行者。一旦绑定后,在进入代理对象方法调用的时候就会到HelloServiceProxy的代理方法上, 代理方法有三个参数:第一个proxy是代理对象,第二个是当前调用的那个方法,第三个 是方法的参数。比方说,现在HelioServiceimp 1对象(obj)用bind方法绑定后,返回其占 位,我们再调用proxy.sayHello(n张三,那么它就会进入到HelloServiceProxy的invoke() 方法。而invoke参数中第一个便是代理对象proxy,方法便是sayHello,参数是张三。

  我们巳经用HelloServiceProxy类的属性target保存了真实的服务对象,那么我们可以 通过反射技术调度真实对象的方法。

  这里我们演示了 JDK动态代理的实现,并且在调用方法前后都可以加入我们想要的东 西。MyBatis在使用Mapper的时候也是这样做的。

让我们测试一下动态代理,如代码清单6.5所示。

代码清单 6-5: HelloServiceMain.java

public class HelloServiceMain (
  public static void main(String[] args) (
    HelloServiceProxy HelioHandler = new HelloServiceProxy ();
    HelloService proxy = (HelioService)HelloHandler.bind(new HelloServicelmpl());
    proxy. sayHello (”张三”);
  }
}

我们运行它,就可以看到如下运行结果。

 

 

 3、CGLIB动态代理

  JDK提供的动态代理存在一个缺陷,就是你必须提供接口才可以使用,为了克服这个 缺陷,我们可以使用开源框架一CGLIB,它是一种流行的动态代理。

  让我们看看如何使用CGLIB动态代理。HelloService.java和HelloServicelmpl.java都不 需要改变,但是我们要实现CGLIB的代理类。它的实现Methodinterceptor的代理方法如代码清单6.6所示。

代码清单 6-6: HelloServiceCgLib.java

public class HelloServiceCgLib implements Methodlnterceptor ( private Object target;
/***创建代理对象
*
*@param target
*@return
*/
public Object getlnstance(Object target) (
  this.target = target;
  Enhancer enhancer = new Enhancer ();
  enhancer.setSuperclass(this.target.getClass());
  //回调方法
  enhancer.setCallback(this);
  //创建代理对象
  return enhancer.create();
)
@Override
//回调方法
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable (
  System.err.printIn("############## 我是 CGL1B 的动态代理 ###### ######”);
  //反射方法前调用
  System.err.printIn (”我准备说 hello");
  Object returnObj = proxy.invokeSuper(obj args);
  //反射方法后调用
  System.err.printIn (”我说过 hello 了");
  return return Obj;
  }
}

  这样便能够实现CGLIB的动态代理。在MyBatis中通常在延迟加载的时候才会用到 CGLIB的动态代理。有了这些基础,我们就可以更好地论述MyBatis的解析和运行过程了。

二、构建 SqlSessionFactory 过程

  SqISessionFactory是MyBatis的核心类之一,其最重要的功能就是提供创建MyBatis的核心接口 SqlSession,所以我们需要先创建SqlSessionFactory,为此我们需要提供配置文 件和相关的参数。而MyBatis是一个复杂的系统,采用构造模式去创建SqlSessionFactory, 我们可以通过SqlSessionFactoryBuilder去构建。构建分为两步。

   第一步,通过 org.apache.ibatis.builder.xml.XMLConfigBuilder 解析配置的 XML 文件, 读出配置参数,并将读取的数据存入这个org.apache.ibatis.session.Conflguration类中。注意,MyBatis几乎所有的配置都是存在这里的。

  第二步,使用 Confinguration 对象去创建 SqlSessionFactory。MyBatis 中的 SqlSessionFactory 是一个接口,而不是实现类,为此MyBatis提供了一个默认的SqlSessionFactory实现类,我们 一般都会使用它 org.apache.ibatis.session.defaults.DefhultSqlSessionFactory。注意,在大部分 情况下我们都没有必要自己去创建新的SqlSessionFactory的实现类。

  这种创建的方式就是一种Builder模式。对于复杂的对象而言,直接使用构造方法构建 是有困难的,这会导致大量的逻辑放在构造方法中,由于对象的复杂性,在构建的时候, 我们更希望一步步有秩序的来构建它,从而降低其复杂性。这个时候使用一个参数类总领 全局,例如,Configuration类,然后分步构建,例如,DefaultSqlSessionFactory类,就可以 构建一个复杂的对象,例如,SqlSessionFactory,这种方式值得我们在工作中学习和使用。

1、构建 Configuration

  在SqlSessionFactory构建中,Configuration是最重要的,它的作用如下。

    • 读入配置文件,包括基础配置的XML文件和映射器的XML文件。
    • 初始化基础配置,比如MyBatis的别名等,一些重要的类对象,例如,插件、映射 器、ObjectFactory typeHandler 对象。
    • 提供单例,为后续创建SessionFactory服务并提供配置的参数。
    • 执行一些重要的对象方法,初始化配置信息。

  显然Confinguration不会是一个很简单的类,MyBatis的配置信息都会来自于此。有兴 趣的读者可以读读源码,几乎所有的配置都可以在这里找到踪影。我们在第2章看到的配 置,全部都会被读入这里并保存为一个单例。Configuration是通过XMLConfigBuilder去构 建的。首先,MyBatis会读出所有XML配置的信息。然后,将这些信息保存到Configuration 类的单例中。它会做如下初始化。

  • properties全局参数。
  • settings 设置。
  • typeAliases 别名。
  • typeHandler类型处理器。
  • Obj ectFactory 对象。
  • plugin 插件。
  • environment 环境。
  • DatabaseldProvider 数据库标识。
  • Mapper映射器。

2、映射器的内部组成

  除了插件外,我们在第2和第3章详细讨论了其他大部分的对象的用法,由于插件需 要频繁访问映射器的内部组成,我们有必要单独研究一下映射器的内部组成,所以这节也 是本章的重点内容之一,使用插件前务必先掌握好本节内容。

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

  • MappedStatement,它保存映射器的一个节点(select|insert|delete|update)包括许多我们配置的 SQL、SQL id、 缓存信息、resultMap、 parameterType、resultType、 languageDriver等重要配置内容。
  • SqlSource,它是提供BoundSql对象的地方,它是MappedStatement的一个属性。
  • BoundSql,它是建立SQL和参数的地方。它有3个常用的属性:SQL、parameterobject parameterMappings,稍后我们会讨论它们。

  这些都是映射器的重要内容,也是MyBatis的核心内容。在插件的应用中常常会用到 它们。映射器的解析过程是比较复杂的,但是在大部分的情况下,我们并不需要去理会解 析和组装SQL的规则,因为大部分的插件只要做很小的改变即可,无需做很大的改变。大 的改变可能导致重写这些内容。所以我们主要关注参数和SQL。

  先看看映射器的内部组成,如图6.3所示。

  注意,这里笔者并没有将所有的方法和属性都列举出来,只列举了主要的属性和方法。

  MappedStatement对象涉及的东西较多,我们一般都不去修改它,因为容易产生不必要 的错误。SqlSource是一个接口,它的主要作用是根据参数和其他的规则组装SQL (包括第5章的动态SQL),这些都是很复杂的东西,好在MyBatis本身已经实现了它,一般也不需 要去修改它。对于参数和SQL而言,主要的规则都反映在BoundSql类对象上,在插件中 往往需要拿到它进而可以拿到当前运行的SQL和参数以及参数规则,做出适当的修改,来 满足我们特殊的需求。

  BoundSql 会提供 3 个主要的属性:parameterMappings、parameterobject sql。

  • 其中parameterobject为参数本身。通过第4章,我们可以传递简单对象,POJO、 Map或者@Param注解的参数,由于它在插件中相当常用我们有必要讨论一下它 的规则。
  • 传递简单对象(包括int、 String、floatdouble等),比如当我们传递int类型时,MyBatis 会把参数变为Integer对象传递,类似的long、String、float、double也是如此。
  • 如果我们传递的是POJO或者Map,那么这个parameterobject就是你传入的POJO 或者Map不变。
  • 当然我们也可以传递多个参数,如果没有@Param注解,那么MyBatis就会把 parameterobject变为一个Map<String, Object>对象,其键值的关系是按顺序来规划 ,类似于这样 的形式{“1": pl,“2”:p2,“3”: p3....,“paraml”:pl, “param2”:p2, “param3”:p3 .....},所以在编写的时候我们都可以使用#{paraml)或者#{1}去引用第 一个参数。
  • 如果我们使用@Param注解,那么MyBatis就会把parameterobject也会变为一个Map<String, Object>对象,类似于没有@Param注解,只是把其数字的键值对应置换 @Param 注解的键值。比如我们注解@Param("keyl ”) String pl, @Param("key2 int p2, @Param("key3") Role p3,那么这个 parameterobject 对象就是一个 Map<String, Object>,它的键值包含:"keyl": pl,“key2": p2, “key3": p3,“paraml”: pl,“param2": p2,"param3”: p3}。
  • parameterMappings,它是一个 List,每一个元素都是 ParameterMapping 的对象。这 个对象会描述我们的参数。参数包括属性、名称、表达式、javaType、jdbcType、 typeHandler等重要信息,我们一般不需要去改变它。通过它可以实现参数和SQL 的结合,以便PreparedStatement能够通过它找到parameterobject对象的属性并设置 参数,使得程序准确运行。
  • sql属性就是我们书写在映射器里面的一条SQL,在大多数时候无需修改它,只有 在插件的情况下,我们可以根据需要进行改写。改写SQL将是一件危险的事情,请务必慎重行事。

3、构建 SqISessionFactory

有了 Configuration对象构建SqISessionFactory就很简单了,我们只要写很简短的代码便可以了。

 

 

   MyBatis会根据Configuration的配置读取所配置的信息,构建SqISessionFactory对象。

三、SqlSession 运行过程

 

  SqlSession的运行过程是本章的重点和难点,也是整个MyBatis最难以理解的部分。

 

  SqlSession是一个接口,使用它并不复杂。我们构建SqISessionFactory就可以轻易地拿到 SqlSession 了。SqlSession给出了查询、插入、更新、删除的方法,在旧版本的MyBatis iBatis中常常使用这些接口方法,而在新版的MyBatis中我们建议使用Mapper,所以它就 MyBatis最为常用和重要的接口之一。

 

  但是,SqlSession内部可没有那么容易,因为它的内部实现相当复杂,在后面我们会详 细讨论其实现方式。本章的每一节都是承上启下的,我们需要一步步地掌握本章内容,一 旦脱节你很容易迷失在过程中。

1、映射器的动态代理

  Mapper映射是通过动态代理来实现的,我们首先来看看代码清单6-7。

代码清单 6・7: MapperProxyFactory.java

public class MapperProxyFactory<T> (

......

@SuppressWarnings("unchecked”) 
protected T newlnstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] ( mapperInterface }, mapperProxy);
}
public T newlnstance(SqlSession sqlSession) {
  final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodcache);
  return newlnstance(mapperProxy);
  }
}

  这里我们可以看到动态代理对接口的绑定,它的作用就是生成动态代理对象(占位)。 而代理的方法则被放到了 MapperProxy类中。

  我们探讨一下MapperProxy的源码,如代码清单6-8所示。

 

 

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

  让我们看看这个execute方法的源码,如代码清单6-9所示。

 

 

 

 

 

   MapperMethod采用命令模式运行,根据上下文跳转,它可能跳转到许多方法中,我们 不需要全部明白。我们可以看到里面的executeForMany方法,再看看它的实现,实际上它 最后就是通过sqlSession对象去运行对象的SQL。

至此,相信大家已经了解了 MyBatis为什么只用Mappper接口便能够运行SQL,因为 映射器的XML文件的命名空间对应的便是这个接口的全路径,那么它根据全路径和方法 名便能够绑定起来,通过动态代理技术,让这个接口跑起来。而后采用命令模式,最后还 是使用SqlSession接口的方法使得它能够执行查询,有了这层封装我们便可以使用接口编 程,这样编程就更简单了。

2、SqlSession下的四大对象

  我们已经知道了映射器其实就是一个动态代理对象,进入到了 MapperMethod的execute 方法。它经过简单判断就进入了 SqlSession的删除、更新、插入、选择等方法,那么这些 方法如何执行呢?这是我们需要关心的问题,也是正确编写插件的根本。

  显然通过类名和方法名字就可以匹配到我们配置的SQL,我们不需要去关心这些细节, 我们关心的是设计框架。Mapper执行的过程是通过Executor 、StatementHandler 、ParameterHandler和ResultHandler来完成数据库操作和结果返回的。

    • Executor 代表执行器,由它来调度 StatementHandler、ParameterHandler、ResultHandler 等来执行对应的SQL。
    • StatementHandler 的作用是使用数据库的 Statement (PreparedStatement)执行操作, 它是四大对象的核心,起到承上启下的作用。
    • ParameterHandler用于SQL对参数的处理。
    • ResultHandler是进行最后数据集(ResultSet)的封装返回处理的。

  下面我们逐一分析讲解这四个对象的生成和运作原理。到这里我们已经来到了 MyBatis 的底层设计,对Java语言基础不牢的读者来说,这将是一次挑战。

(1)执行器

  执行器(Executor)起到了至关重要的作用。它是一个真正执行Java和数据库交互的 东西。在MyBatis中存在三种执行器。我们可以在MyBatis的配置文件中进行选择,具体 请参看3.2节关于setting元素的属性defaultExecutorType的说明。

    • SIMPLE,简易执行器,不配置它就是默认执行器。
    • REUSE,是一种执行器重用预处理语句。
    • BATCH,执行器重用语句和批量更新,它是针对批量专用的执行器。

  它们都提供了查询和更新的方法,以及相关的事务方法。这些和其他框架并无不同,不过我们要了解一下它们是如何构造的,让我们来看看MyBatis如何创建Executor,如代码清单6.10所示。

 

   如同描述的一样,MyBatis将根据配置类型去确定你需要创建三种执行器中的哪一种, 在创建对象后,它会去执行下面这样一行代码。

 

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

 

 

 

   显然 MyBatis 根据 Configuration 来构建 StatementHandler,然后使用 prepareStatement 方法,对SQL编译并对参数进行初始化,我们在看它的实现过程,它调用了 StatementHandler 的prepare。进行了预编译和基础设置,然后通过StatementHandler的parameterize()来设置 参数并执行,resultHandler再组装查询结果返回给调用者来完成一次查询。这样我们的焦 点又转移到了 StatementHandler上。

(2)数据库会话器

 

  顾名思义,数据库会话器(StatementHandler)就是专门处理数据库会话的,让我们 先来看看MyBatis是如何创建StatementHandler的,再看Configuration.java生成会话器的 地方,如代码清单6-12所示。

 

 

 

   很显然创建的真实对象是一个RoutingStatementHandler对象,它实现了接口 StatementHandlero和Executor一样,用代理对象做一层层的封装,第7章我们会讨论它。

  RoutingStatementHandler不是我们真实的服务对象,它是通过适配模式找到对应的 StatementHandler 来执行的。在 MyBatis 中,StatementHandler Executor 一样分为三种: SimpleStatementHandler> PreparedStatementHandler> CallableStatementHandlero 它所对应的 6.3.2.1节讨论的三种执行器。

  在初始化RoutingStatementHandler对象的时候它会根据上下文环境决定创建哪个 StatementHandler对象,我们看看RoutingStatementHandler的源码,如代码清单6-13所示。

 

 

 

   数据库会话器定义了一个对象的适配器delegate,它是一个StatementHandler接口对象, 构造方法根据配置来适配对应的StatementHandler对象。它的作用是给实现类对象的使用 提供一个统一、简易的使用适配器。此为对象的适配模式,可以让我们使用现有的类和方法 对外提供服务,也可以根据实际的需求对外屏蔽一些方法,甚至是加入新的服务。

  我们现在以最常用的PreparedStatementHandler为例,看看MyBatis是怎么执行查询的。 在讲解执行器时我们看到了它的三个主要的方法,prepare> parameterize和query,如代码 清单6.14所示。

 

   instantiateStatement()方法是对SQL进行了预编译。首先,做一些基础配置,比如超时,获取的最大行数等的设置。然后,Executor会调用parameterize()方法去设置参数,它的方 法如代码清单6.15所示。

 

   这个时候它是调用ParameterHandler去完成的,6.3.23节我们将讨论如何使用它,这 里先学习StatementHandler的查询方法吧,如代码清单6-16所示。

 

   由于在执行前参数和SQL都被prepare()方法预编译,参数在parameterize。方法上已经 进行了设置。所以到这里已经很简单了。我们只要执行SQL,然后返回结果就可以了。执 行之后我们看到了 ResultSetHandler对结果的封装和返回。

到了这里我们就很清楚一条查询SQL的执行过程了。

  Executor会先调用StatementHandler的prepare()方法预编译SQL语句,同时设置一* 基本运行的参数。然后用parameterize。方法启用ParameterHandler设置参数,完成预编译, 跟着就是执行查询,而update。也是这样的,最后如果需要查询,我们就用ResultSetHandler 封装结果返回给调用者。

  这样我们就清楚了执行SQL的流程了,很多东西都已经豁然开朗,下面我们再讨论另外两个对象的使用,那就是 ParameterHandler ResultSetHandler。

(3)参数处理器

 

  我们在6.3.2.2节中看到了 MyBatis是通过参数处理器(ParameterHandler)对预编译语 句进行参数设置的。它的作用是很明显的,那就是完成对预编译参数的设置。让我们先看它的定义,如代码清单6.17所示。

 

   其中,getParameterObject()法的作用是返回参数对象,setParameters()方法的作用是 设置预编译SQL语句的参数。

  MyBatis ParameterHandler 提供了一个实现类 DefoultParameterHandler,我们来看看 setParameters的实现,如代码清单6-18所示。

 

 

 

   我们可以看到它还是从parameterobject对象中取参数,然后使用typeHandler进行参数 处理,这就和第3章的typeHandler配置一样,如果你有设置,那么它就会根据签名注册的 typeHandler对参数进行处理。而typeHandler也是在MyBatis初始化的时候,注册在 Configuration里面的,我们需要的时候可以直接拿来用。这样就完成了参数的设置。

(4)、结果处理器

  有了 StatementHandler的描述,我们知道它就是组装结果集返回的。我们再来看看结 果处理器(ResultSetHandler)的接口定义,如代码清单6・19所示。

 

   其中,handleOutputParameters()方法是处理存储过程输出参数的,我们暂时不必管它, 重点看一下handleResultSets()方法,它是包装结果集的。MyBatis同样为我们提供了一个 DefaultResultSetHandler类,在默认的情况下都是通过这个类进行处理的。这个实现有些复 杂,它涉及使用JAVASSIST或者CGLIB作为延迟加载,然后通过typeHandler ObjectFactory进行组装结果再返回,笔者就不详细论述了,因为我们需要改变它们的概率

我们现在清楚了一个SqlSession通过Mapper运行方式的运行原理,而通过SqlSession 接口的查询、更新等方法也是类似的。至此,我们已经明确MyBatis底层的SqlSession 的秘密,也了解了它的工作原理。这为我们学习插件的运行奠定了坚实的基础。

3、SqlSession 运行总结

  SqlSession的运行原理十分重要,它是插件的基础,这里我们对一次查询或者更新进行总结以加深对MyBatis内部运行的掌握。SqlSession内部运行图,如图6・4所示。

 

 

  SqlSession 是通过 Executor 创建 StatementHandler 来运行的,而 StatementHandler 要经过下 面三步。

    • prepared 预编译 SQL。
    • parameterize 设置参数。
    • query/update 执行 SQL。

  其中parameterize是调用parameterHandler的方法去设置的,而参数是根据类型处理器 typeHandler去处理的。query/update方法是通过resultHandler进行处理结果的封装,如果是 date的语句,它就返回整数,否则它就通过typeHandler处理结果类型,然后用 ObjectFactory提供的规则组装对象,返回给调用者。这便是SqlSession执行的过程,我们 清楚了四大对象是如何协作的,同时也更好地理解了 typeHandler和ObjectFactory MyBatis中的应用。

 

 

 

 

 

 

posted @ 2020-09-04 11:03  跃小云  阅读(322)  评论(0编辑  收藏  举报