第二章-Mybatis源码解析-以xml方式走流程

第二章-Mybatis源码解析-以xml方式走流程

2.1 准备阶段

xml执行大概经过以下几个步骤:

1)xml配置资源位置

2)根据配置资源构建Environment(环境)、Configuration(配置信息)、TransactionFactory(事务工厂类)、typeAliases(类型别名)、typeHandlers(类型处理器)

3)生成SqlSessionFactory

4)创建SqlSession

5)包装语句(包括xml语句的解析)

6)处理类型

7)处理参数

8)执行语句(涉及缓存)

9)处理结果

10)包装结果并返回,结束流程

实际流程比上面的还是要复杂很多,但是Mybatis帮我们都封装的很好,实际使用时,就几行代码,如下:

java 复制代码
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
Blog blog = sqlSession.selectOne("polyphagic.code.mybatis.BlogMapper.selectBlog1", 1);

那我们结合流程和以上的几行代码来看看Mybatis到底做了什么。

String resource = "mybatis-config.xml";

InputStream inputStream = Resources.getResourceAsStream(resource);

这两行代码的作用是根据地址信息,查找xml配置资源,并转化成输入流

引用官方文档的一句话: 每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的。SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得。而 SqlSessionFactoryBuilder 则可以从 XML 配置文件或一个预先配置的 Configuration 实例来构建出 SqlSessionFactory 实例。

从 XML 文件中构建 SqlSessionFactory 的实例非常简单,建议使用类路径下的资源文件进行配置。 但也可以使用任意的输入流(InputStream)实例,比如用文件路径字符串或 file:// URL 构造的输入流。MyBatis 包含一个名叫 Resources 的工具类,它包含一些实用方法,使得从类路径或其它位置加载资源文件更加容易。

好,那我们看看 new SqlSessionFactoryBuilder().build(inputStream) 这个方法做了什么

java 复制代码
public SqlSessionFactory build(InputStream inputStream) {
    return build(inputStream, null, null);
}

 public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      // 最终看这一行,先做xml解析,然后再build构建,继续往里面深挖
      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.
      }
    }
  }

XML配置信息解析

xml的解析过程读者可以去看看有关xml解析的文章,我这里就不讲xml如何解析xml文件和结构。解析完后,就能得到完整的Configuration对象,里面包括typeAliases、typeHandler、映射器mapper、全局属性、日志实现、插件、环境等内容,后续流程中都会用到这些。

java 复制代码
public Configuration parse() {
    if (parsed) { // 重复解析判断
        throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true; // 设置解析标志
    // 从xml的configuration的节点开始解析
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
}

private void parseConfiguration(XNode root) {
    try {
        //issue #117 read properties first
        // 首先解析properties
        propertiesElement(root.evalNode("properties"));
        // 解析settings 
        Properties settings = settingsAsProperties(root.evalNode("settings"));
        /*
        通过settings中的配置,拿到vfs的实现,并设置到configuration中
        这个vfs网上很多文章都说是"虚拟文件系统",其实这压根就不是什么文件系统,这个我们可以看VFS的类实现逻辑和提供的方法入口来看,实际上它只是用于更方便的访问应用程序服务器中的资源,比如查找类文件实现定义时,需要列出某个目录的地址列出所有文件来查找;判断某个文件是不是jar文件等等;其实把它理解成一个工具类就行,扯上"虚拟文件系统"这个概念就有点大了
        */
        loadCustomVfs(settings);
        /*
        设置日志实现方式:SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING
        */
        loadCustomLogImpl(settings);
        // 类型别名解析,解析后的类型别名都存放在类TypeAliasRegistry的typeAliases(HashMap)中,读者有兴趣的可以去看下这个类的,里面包括了8个基础类型及其数组和包装类和数组、日期类型、数字类型、Object类型、List、Map、HashMap、ArrayList等常用类型的另外定义,这就是为什么我们在xml写sql语句时,类型Map可以直接写,而不需要写成全限定名的原因。
        typeAliasesElement(root.evalNode("typeAliases"));
        // 解析自定义的插件设置,具体细节等实际做插件处理时再讲
        pluginElement(root.evalNode("plugins"));
        // 对象工厂解析:每次 MyBatis 创建结果对象的新实例时,它都会使用一个对象工厂(ObjectFactory)实例来完成实例化工作。 默认的对象工厂需要做的仅仅是实例化目标类,要么通过默认无参构造方法,要么通过存在的参数映射来调用带有参数的构造方法。 如果想覆盖对象工厂的默认行为,可以通过创建自己的对象工厂来实现,这块自定义实现,官方文档中有描述。
        objectFactoryElement(root.evalNode("objectFactory"));
        // 解析对象包装类工厂配置
        objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
        // 解析反射器工厂配置
        reflectorFactoryElement(root.evalNode("reflectorFactory"));
        // 将settings中设置的内容都设置到 configuration 中,有兴趣的可以回头看官网对settings可设置的内容详细了解
        settingsElement(settings);
        // read it after objectFactory and objectWrapperFactory issue #631
        // 像出现 issue #631 这种注释时,就表示是为了解决编号631的bug,这是业界规范描述
        // 解析环境设置,MyBatis 可以配置成适应多种环境,这种机制有助于将 SQL 映射应用于多种数据库之中, 现实情况下有多种理由需要这么做。例如,开发、测试和生产环境需要有不同的配置;或者想在具有相同 Schema 的多个生产数据库中使用相同的 SQL 映射。还有许多类似的使用场景。
        environmentsElement(root.evalNode("environments"));
        // 解析数据库厂商标识配置
        databaseIdProviderElement(root.evalNode("databaseIdProvider"));
        // 解析类型处理器配置:类型处理器也都是通过类TypeHandlerRegistry中的HashMap来维护的,里面有定义Mybatis帮我们预先设置的许多默认数据类型的类型处理器,读者可以打开TypeHandlerRegistry类源码看一下就知道,这块内容也可以自定义
        typeHandlerElement(root.evalNode("typeHandlers"));
        // 解析映射器设置,映射器说白了,就是写sql的地方,Mybatis支持xml和注解形式,所以,这里可以配置两种形式,一是xml文件目录;二是Java接口类文件目录,Mybatis官网也有描述,可以自己去看,这一步还实现了对sql语句的进一步解析,这块可以看`章节2.2`
        mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
        throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
}

构建SqlSessionFactory

上面解析配置信息后,就要构建SqlSessionFactory了,通过类SqlSessionFactoryBuilder来构建,看如下代码

java 复制代码
  public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config); // 实际上就是创建DefaultSqlSessionFactory实例
  }

2.2 映射器配置解析

代码实现在XMLConfigBuilder.mapperElement方法中

java 复制代码
private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        if ("package".equals(child.getName())) {
          // 将包内的映射器接口实现全部注册为映射器
          String mapperPackage = child.getStringAttribute("name");
          configuration.addMappers(mapperPackage);
        } else {
          // 使用相对于类路径的资源引用
          String resource = child.getStringAttribute("resource");
          // 使用完全限定资源定位符(URL)
          String url = child.getStringAttribute("url");
          // 使用映射器接口实现类的完全限定类名
          String mapperClass = child.getStringAttribute("class");
          if (resource != null && url == null && mapperClass == null) {
            ErrorContext.instance().resource(resource);
            InputStream inputStream = Resources.getResourceAsStream(resource);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            // mapper文件内容解析,主要就是sql、参数map、结果map、缓存配置的解析,看`图2-1`,后续章节会对这块内容做细致的解析工作
            mapperParser.parse();
          } else if (resource == null && url != null && mapperClass == null) {
            ErrorContext.instance().resource(url);
            InputStream inputStream = Resources.getUrlAsStream(url);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
            // mapper文件内容解析,主要就是sql、参数map、结果map、缓存配置的解析,看`图2-1`,后续章节会对这块内容做细致的解析工作
            mapperParser.parse();
          } else if (resource == null && url == null && mapperClass != null) {
            // 加载mapper接口类
            Class<?> mapperInterface = Resources.classForName(mapperClass);
            // 添加到configuration的mapperRegistry注册器的knownMappers(HashMap)中,里面也涉及对mapper接口类的解析,主要应用在注解方式,所以留待后续讲解注解方式时再讲
            configuration.addMapper(mapperInterface);
          } else {
            throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
          }
        }
      }
    }
  }

​ 图2-1

后续将通过抖音视频/直播的形式分享技术,由于前期要做一些准备和规划,预计2024年6月开始,欢迎关注,如有需要或问题咨询,也可直接抖音沟通交流。

相关推荐
七星静香20 分钟前
laravel chunkById 分块查询 使用时的问题
java·前端·laravel
Jacob程序员21 分钟前
java导出word文件(手绘)
java·开发语言·word
ZHOUPUYU21 分钟前
IntelliJ IDEA超详细下载安装教程(附安装包)
java·ide·intellij-idea
stewie625 分钟前
在IDEA中使用Git
java·git
Elaine20239140 分钟前
06 网络编程基础
java·网络
G丶AEOM42 分钟前
分布式——BASE理论
java·分布式·八股
落落鱼201342 分钟前
tp接口 入口文件 500 错误原因
java·开发语言
想要打 Acm 的小周同学呀43 分钟前
LRU缓存算法
java·算法·缓存
镰刀出海1 小时前
Recyclerview缓存原理
java·开发语言·缓存·recyclerview·android面试
阿伟*rui3 小时前
配置管理,雪崩问题分析,sentinel的使用
java·spring boot·sentinel