• 第二章-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帮我们都封装的很好,实际使用时,就几行代码,如下:

    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);
    
    • 1
    • 2
    • 3
    • 4
    • 5

    那我们结合流程和以上的几行代码来看看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) 这个方法做了什么

    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.
          }
        }
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    XML配置信息解析

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

    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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52

    构建SqlSessionFactory

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

      public SqlSessionFactory build(Configuration config) {
        return new DefaultSqlSessionFactory(config); // 实际上就是创建DefaultSqlSessionFactory实例
      }
    
    • 1
    • 2
    • 3

    2.2 映射器配置解析

    代码实现在XMLConfigBuilder.mapperElement方法中

    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.");
              }
            }
          }
        }
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    ​ 图2-1
    在这里插入图片描述

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

  • 相关阅读:
    这些专业配音软件你值得拥有
    2022.03青少年软件编程(Python)等级考试试卷(五级)
    web开发理论测试题
    猿创征文|HCIE-Security Day54:anti-ddos设备防御原理
    Azure DevOps (五) 推送流水线制品到流水线仓库
    ZLMediaKit - webrtc录像
    Minianaconda安装jupyter notebook遇到的问题及解决
    day 6
    spring-boot---validation,参数校验,分组,嵌套,各种类型
    VUE项目中页面权限和按钮权限
  • 原文地址:https://blog.csdn.net/zhang527294844/article/details/136304819