第二章-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月开始,欢迎关注,如有需要或问题咨询,也可直接抖音沟通交流。

相关推荐
万琛7 分钟前
【java-Neo4j 5开发入门篇】-最新Java开发Neo4j
java·neo4j
Bald Baby26 分钟前
JWT的使用
java·笔记·学习·servlet
刘大浪27 分钟前
后端数据增删改查基于Springboot+mybatis mysql 时间根据当时时间自动填充,数据库连接查询不一致,mysql数据库连接不好用
数据库·spring boot·mybatis
魔道不误砍柴功31 分钟前
实际开发中的协变与逆变案例:数据处理流水线
java·开发语言
dj24429457071 小时前
JAVA中的Lamda表达式
java·开发语言
工业3D_大熊1 小时前
3D可视化引擎HOOPS Luminate场景图详解:形状的创建、销毁与管理
java·c++·3d·docker·c#·制造·数据可视化
szc17671 小时前
docker 相关命令
java·docker·jenkins
程序媛-徐师姐1 小时前
Java 基于SpringBoot+vue框架的老年医疗保健网站
java·vue.js·spring boot·老年医疗保健·老年 医疗保健
yngsqq1 小时前
c#使用高版本8.0步骤
java·前端·c#