MyBatis源码分析之核心处理层

MyBatis源码分析之核心处理层_第1张图片

mybatis与数据库进行交互有两种方式,一种传统方式,一种mapper代理方式。通过对两种方式的分析我们需要掌握以下内容:

  • 传统方式
  1. MyBatis如何加载解析配置文件?
  2. MyBatis如何解析SQL、设置参数以及执行SQL的?
  3. MyBatis如何封装返回结果集?
  • mapper代理方式
  1. MyBatis底层如何产生代理对象?
  2. 当代理对象调用方法时它又是如何执行到底层的JDBC代码的?

1 传统方式源码剖析

类似于Spring、MyBatis等灵活性和可拓展性都很高的开源框架都提供了很多配置项,开发人员需要在使用时提供相应的配置信息,实现相应的需求。MyBatis中的配置文件主要有两个,分别是mybatis-config.xml和映射配置文件。现在主流的配置方式除了使用XML配置文件,还会配合注解进行配置。

1.1 初始化流程

Mybatis初始化的主要工作就是加载并解析mybatis-config.xml配置文件、映射配置文件以及相关的注解信息。在引入了MyBatis依赖的项目中我们可以通过以下两行代码执行初始化操作。

InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
//这一行代码就是初始化的开始
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
复制代码

Mybatis的初始化入口就是
SqlSessionFactoryBuilder.build()方法。再来看看build()方法里做了什么。

    // 1.我们最初调用的build
    public SqlSessionFactory build(InputStream inputStream) {
        //调用了重载方法
        return build(inputStream, null, null);
    }
    
    // 2.调用的重载方法
    public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
        try {
            // 创建 XMLConfigBuilder, XMLConfigBuilder是专门解析mybatis的配置文件的类
            XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
            // 执行 XML 解析
            // 创建 DefaultSqlSessionFactory 对象。parse()方法返回的是Configuration对象。
            return build(parser.parse());
        } catch (Exception e) {
            throw ExceptionFactory.wrapException("Error building SqlSession.", e);
        } finally {
            ErrorContext.instance().reset();
            try {
                inputStream.close();
            } catch (IOException e) {
                // Intentionally ignore. Prefer previous error.
            }
        }
    }
复制代码

从上面的源码可以看出,build()方法很简单,只是调用了一下重载的build()方法,具体细节可以看注释。接下来我们开始分析XMLConfigBuilder的parse()方法以及Configuration对象。MyBatis在初始化的时候,会将MyBatis的配置信息全部加载到内存中,使用Configuration对象实例来存储。Configuration对象的结构和xml配置文件几乎相同,也就是说XML文件中的properties(属性)、setting(设置)、typeAliases(类型别名)和typeHandlers(类型处理器)等配置标签在Configuration对象中都有对应的属性来封装它们。也就是说,初始化配置文件信息的本质就是创建Configuration对象,将解析的XML数据封装到Configuration内部属性中。

    /**
     * 解析 XML 成 Configuration 对象。
     *
     * @return Configuration 对象
     */
    public Configuration parse() {
        // 若已解析,抛出 BuilderException 异常
        if (parsed) {
            throw new BuilderException("Each XMLConfigBuilder can only be used once.");
        }
        // 标记已解析
        parsed = true;
        ///parser是XPathParser解析器对象,读取节点内数据,是MyBatis配置文件中的顶层标签
        // 解析 XML configuration 节点
        parseConfiguration(parser.evalNode("/configuration"));
        return configuration;
    }

    /**
     * 解析 XML
     *
     * 具体 MyBatis 有哪些 XML 标签,参见 《XML 映射配置文件》http://www.mybatis.org/mybatis-3/zh/configuration.html
     *
     * @param root 根节点
     */
    private void parseConfiguration(XNode root) {
        try {
            //issue #117 read properties first
            // 解析  标签
            propertiesElement(root.evalNode("properties"));
            // 解析  标签
            Properties settings = settingsAsProperties(root.evalNode("settings"));
            // 加载自定义的 VFS 实现类
            loadCustomVfs(settings);
            // 解析  标签
            typeAliasesElement(root.evalNode("typeAliases"));
            // 解析  标签
            pluginElement(root.evalNode("plugins"));
            // 解析  标签
            objectFactoryElement(root.evalNode("objectFactory"));
            // 解析  标签
            objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
            // 解析  标签
            reflectorFactoryElement(root.evalNode("reflectorFactory"));
            // 赋值  到 Configuration 属性
            settingsElement(settings);
            // read it after objectFactory and objectWrapperFactory issue #631
            // 解析  标签
            environmentsElement(root.evalNode("environments"));
            // 解析  标签
            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);
        }
    }
复制代码

从源码中可以看到,程序是通过parseConfiguration()实现了对mybatis-config.xml配置文件中的每个节点进行解析的,每个节点的解析过程都封装成了一个单独的方法,代码清爽简洁。

程序在解析节点时,会创建XMLMapperBuilder对象加载映射文件,如果映射配置文件配置的是相应的Mapper接口,也会加载相应的Mapper接口,解析其中的注解并向MapperRegistry完成注册。在解析标签的时,程序也会完成对SQL的解析,并将解析后的SQL节点信息封装成MappedStatement对象(描述一条SQL语句),然后存入Configuration对象中的一个HashMap类型的属性mappedStatements中,map的key即是${namespace}.${id}(全限定类名 + 方法名),value对应的MappedStatement对象。

在解析SQL节点中,有两个难点,一个是解析动态SQL,另一个是解析节点,此处只讲整体解析流程,这两个知识点后面以面试题形式单独文章分析。

    /**
     * MappedStatement 映射
     *
     * KEY:`${namespace}.${id}`
     */
    protected final Map mappedStatements = new StrictMap<>("Mapped Statements collection");
复制代码

MappedStatement包含了SQL节点的很多属性,其中比较重要的字段如下:

/**
 * 映射的语句,每个