Mybatis的解析和基本运行原理

Mybatis运行过程

Mybatis的运行过程分为两大步:第1步,读取配置文件缓存到Configuration对象,用于创建SqlSessionFactory;第2步,SqlSession的执行过程。相对而言,SqlSessionFactory的创建还算比较容易理解,而SqlSession的执行过程就不那么简单了,它包括许多复杂的技术,要先掌握反射技术和动态代理,这里主要用到的是JDK动态代理,不熟悉的话请点击这里了解一下

构建SqlSessionFactory过程

SqlSessionFactory是Mybatis的核心类之一,其最重要的功能就是提供创建MyBatis的核心接口SqlSession,所以首先创建SqlSessionFactory,为此要提供配置文件和相关的参数。MyBatis是一个复杂的系统,它采用了Builder模式去创建SqlSessionFactory,在实际中课通过SqlSessionFacyoryBuilder去创建,构建分为两步:

 1. 通过org.apache.ibatis.builder.xml.XMLConfigBuilder解析配置的XML文件读取所配置的参数,
   并将读取的内容存入Configuration类对象中。而Configuration采用的是单例模式,几乎所有的MyBatis
   配置内容都会存放在这个单例对象中

 2. 使用Configuration对象去创建SqlSessionFactory。MyBatis中的SqlSessionFactory是一个接口,
   而不是一个实现类,为此MyBatis提供了一个默认的实现类DefaultSqlSessionFactory.在大部分情况下
   没有必要自己去创建新的实现类。
     这种创建的方式就是一种Builder模式,对于复杂的对象而言,使用构造器参数很难实现,这是使用一个类(比如Configuration)
   作为统领,一步步构建所需的内容,然后通过它去创建最终的对象(比如SqlSessionFactory),这样每一步都会很清晰

XMLConfigBuilder解析Xml的源码:

 private void parseConfiguration(XNode root) {
    try {
      propertiesElement(root.evalNode("properties")); //issue #117 read properties first
      typeAliasesElement(root.evalNode("typeAliases"));
      pluginElement(root.evalNode("plugins"));
      objectFactoryElement(root.evalNode("objectFactory"));
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      settingsElement(root.evalNode("settings"));
      environmentsElement(root.evalNode("environments")); // read it after objectFactory and objectWrapperFactory issue #631
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      typeHandlerElement(root.evalNode("typeHandlers"));
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

      从源码中可以看到,他是通过一步步解析XML内容得到对应的信息,而这些信息正是我们在配置文件中配置的内容,
    这里只讨论typeHandlers解析的方法,其他的类似
      配置的typeHandler都会被注册到typeHandlerRegister对象中去,它的定义是放在XMLConfiguration的父类
    BaseBuilder中的,代码如下:

     protected final TypeHandlerRegistry typeHandlerRegistry;
      public BaseBuilder(Configuration configuration) {
        this.configuration = configuration;
        this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
        this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
      }
    可以看出typeHandlerRegistry是Configuration单例的一个属性,所以可以通过COnfiguration单例拿到typeHandlerRegistry,
    从而进行注册typeHandler

构建Configuration

在SqlSessionFactory构建中,Configuration是最重要的,它的作用是:

  • 读入配置文件,包括基础配置的Xml和映射器XML(或注解)
  • 初始化一些基础配置,比如Mybatis别名等,一些重要的类对象(如插件、映射器Object工厂等)
  • 提供单例,为后续的创建SessionFactory服务提供配置的参数
    显然Configuration不会是一个简单的类,MyBatis的配置信息都来自于此,首先他会读出XML配置的信息,然后把它们保存在Configuration单例中,它会做如下初始化:
  • properties全局参数
  • typeAliases别名
  • Plugins 插件
  • objectFactory 对象工厂
  • reflectionFactory 反射工厂
  • settings 环境设置
  • environment 数据库环境
  • databaseIdProvider 数据库标识
  • Mappers 映射器

    他们都会以类似typeHandler注册那样的方法被存放到Configuration单例,以便将来取出

构建映射器的内部组成

当XMLConfiguration解析XML是会将每一个SQL和其配置的内容保存起来。一般而言,在MyBatis中一条SQL和它相关的配置是有3个部分组成的,它们分别是MappedStatement、SqlSource和BoundSql。

  • MappedStatement的作用保存一个映射器节点(select | insert | delete | update)的内容。它是一个类,包括许多我们配置的SQL、SQL的id、缓存信息、resultMap、parameterType、resultType等重要的配置信息,它还有一个重要的属性sqlSource。MyB通过它来获取某条SQL配置的所有信息。
  • SqlSource 是提供BoundSql对象的地方,它是MappedStatement的一个重要属性。注意,它是一个接口,而不是一个实现类。它有几个重要的实现类:DynamicSqlSource、ProviderSqlSource、RawSqlSource、StaticSqlSource。它的作用是根据上下文和参数解析生成需要的SQL,这个接口只定义了一个接口方法—–getBoundSql(parameterObject),使用它就可以得到一个BoundSql对象。
  • BoundSql是一个结果对象,也就是SqlSource通过对SQL和参数的解析得到的SQL和参数,它是建立SQL和参数的地方,他有常用的3个属性:sql、parameterObject、parameterMappings
  • 映射器内部组成图:
    组成图
    这里只是列举了主要的方法和属性。MappedStatement对象涉及的东西比较多,一般不去修改它,因为容易产生不必要的错误。SqlSource是一个接口,它的这个主要作用是根据参数和其他的规则组装SQL,这些都是很复杂的东西,好在MyBatis本身已经实现了它,一般不需要去修改。对于最终的参数和SQL都反映在BoundSql类对象上,在插件中往往需要拿到它进而拿到当前运行的SQL和参数,从而对运行过程做出必要的修改,来满足特殊的需求,这里的讨论对MyBatis插件的开发是至关重要的
由上图可知BoundSql会提供3个主要的属性:parameterMappings,parameterObject和sql
  • parameterObject为参数本身,可以传递简单对象、POJO、Map或@Param注解的参数,由于它在插件中相当有用,有必要讨论它的一些规则
    ①.传递简单对象,包括int、String、float、double等。当传递int类型时,MyBatis会把参数变为Integer对象传递,类似的long、String、float、double也是如此。
    ②.传递POJO或者Map,parameterObject就是传入的POJO或者Map
    ③.传递多个参数,如果没有使用@Param注解,那么MyBatis会把parameterObject变成一个Map< String,Object>对象,其键值的关系是按照顺序来规划的,类似于{“1”:p1, “2”:p2, “3”:p3……,”param1”:p1, “param2”:p2, “param3”:p3…}这样的形式。所以在编写的时候可以使用#{param1}或者#{1}去引用第一个参数。
    ④.使用@Param注解,MyBatis就会把parameterObject也变成一个Map< String,Object>对象,类似于没有@Param注解,只是把其数字的键值置换成@Param注解键值。比如注解@Param(“key1”) String p1、@Param(“key2”) int p2、@Param(“key3”) User p3,那么parameterObject对象就是一个Map对象,它的键值包含{“key1”:p1,”key2”:p2, “key3”:p3,”param1”:p1, “param2”:p2,”param3”:p3}

  • parameterMappings是一个List,它的每一个元素都是ParameterMapping对象。对象会描述参数,参数包括属性名称、表达式、javaType、jdbcType、typeHandler等重要信息,一般不需要去改变他,通过它就可以实现参数和SQL的结合,以便于PreparedStatement能够通过它找到paramterObject对象的属性设置参数,使得程序能准确运行。

  • sql属性就是书写在映射器里面的一条被SqlSource解析后的SQL,大部分情况下无需修改它,只是在使用插件是可以根据需求进行改写。

    构建SqlSessionFactory

    有了Configuration对象,构建SqlSessionFactory是很简单的。

 sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

这里写图片描述
由图上的源码和之前的分析,可以得知MyBatis会根据文件流先生成Configuration对象,进而构建SqlSessionFactory对象,真正的难点在于构建Configuration对象,所以关注的重心实际应该是Configuration对象,而不是SqlSessionFactory对象。

扫描二维码关注公众号,回复: 1656188 查看本文章

SqlSession运行过程

映射器(Mapper)的动态代理

在代码中常看到这样一句代码:

UserMapper userMapper = SqlSession.getMapper(UserMapper.class); 

查看Mybatis源码如何实现getMapper方法的:
实现方法
由源码可以得知,它运用了Configuration的对象的getMapper方法,继续往下看:
cfg_mapper
显然,它有用到了Mapperregistry来获取对应的接口对象,源码如下:

首先它判断是否注册一个Mapper,没有的话则抛出异常,如果有,就会启用MapperProxyFactory工厂来生成一个代理对象,继续追踪源码:
这里写图片描述
注意红圈的代码,Mapper映射是通过动态代理来实现的,这里可以看到动态代理对接口的绑定,它的作用就是生成动态代理对象,而代理的方法则是放到了MapperProxy类中,因此再看一下MapperProxy的源码:

可以看到这里的invoke逻辑,如果Mapper是一个动态代理对象,那么它就会运行到invoke方法里面,invoke方法首先判断是否是一个类,这里的Mapper是一个接口,所以判断失败。然后会生成MapperMethod对象,它是通过cachedMapperMethod方法对其初始化,最后执行execute方法,把SqlSession和当前的参数传递进去。execute方法的源码如下:
execute这里写图片描述
这里不需要全部明白所有方法的含义,只要讨论executeForMany方法的实现即可。通过源码我们可以知道,实际上它最后就是通过SqlSession对象去运行SQL,其他的增删改查也是类似这样处理的。
所以,现在就清楚MyBatis为什么只用Mapper接口便能够运行起来了,因为Mapper的XML文件的命名空间对应的是这个接口的全限定名,而方法就是那条SQL的id,这样MyBatis就可以根据全路径和方法名,将其和代理对象绑定起来,通过动态代理技术,让这个接口运行起来,而后采用命令模式。最后使用SQlSession接口的方法使得它能够执行对应的SQL,只是有了这层封装,就可以采用接口编程,这样的编程更为简单明了。

猜你喜欢

转载自blog.csdn.net/sjzao/article/details/79474205
今日推荐