MyBatis 源码解析MyBatis如何解析配置 ?(六)

2023-11-12

XMLMapperBuilder###parameterMapElement()

//代码比较长了,因为parameterMap 涉及到比较多的东西  
//    <resultMap id="userMap" type="com.test.demo.model.SysUser">
//        <id property="id" column="id"/>
//        <result property="userPassword" column="user_password"/>
//        <result property="userName" column="user_name"/>
//        <result property="createTime" column="create_time" jdbcType="TIMESTAMP"/>
//    </resultMap>
private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings, Class<?> enclosingType) throws Exception {
    //首先记录一下跟踪日志,有关ErrorContext后面会详细明说
    ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
    //这里可以看到用了一连串的嵌套,其实就是设置默认值而已
    //简单解释这里的操作就是:首先获取`type`的值
    //如果`type`为空就取`ofType`的值
    //如果`ofType`为空就取`resultType`的值
    //如果`resultType`为空就取`javaType`的值
    String type = resultMapNode.getStringAttribute("type",
        resultMapNode.getStringAttribute("ofType",
            resultMapNode.getStringAttribute("resultType",
                resultMapNode.getStringAttribute("javaType"))));
    //解析这个类
    Class<?> typeClass = resolveClass(type);
    //如果type为null
    //一般resolveClass只有在type为null的时候才会返回null
    if (typeClass == null) {
      //这里暂时没看懂,因为解析`ResultMap`传入的enclosingType为null  
      typeClass = inheritEnclosingType(resultMapNode, enclosingType);
    }
    Discriminator discriminator = null;
    List<ResultMapping> resultMappings = new ArrayList<>();
    resultMappings.addAll(additionalResultMappings);
    //解析子节点
    List<XNode> resultChildren = resultMapNode.getChildren();

    for (XNode resultChild : resultChildren) {
      //单独处理构造函数节点
      if ("constructor".equals(resultChild.getName())) {
        //处理构造函数节点  
        processConstructorElement(resultChild, typeClass, resultMappings);
         //单独处理鉴定器节点 
      } else if ("discriminator".equals(resultChild.getName())) {
        discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
      } else {
        List<ResultFlag> flags = new ArrayList<>();
        //如果子节点为<id>,则将其保存起来,后续用来充当equals的作用 
        if ("id".equals(resultChild.getName())) {
          flags.add(ResultFlag.ID);
        }
        //处理其他的子节点  
        resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
      }
    }
    //获取此节点的id
    String id = resultMapNode.getStringAttribute("id",
            //不存在则自动生成一个唯一id                            
            resultMapNode.getValueBasedIdentifier());
    //获取继承节点信息
    String extend = resultMapNode.getStringAttribute("extends");
    //是否自动映射
    Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
    //创建解析类
    ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
    try {
      //进行解析  
      return resultMapResolver.resolve();
    } catch (IncompleteElementException  e) {
      configuration.addIncompleteResultMap(resultMapResolver);
      throw e;
    }
  }

说实话,这段代码是在比较难理解,就光前面的嵌套获取type \ ofType \ jdbcType 等找了十万遍都没找到为什么。。甚至想到了是不是兼容老版本的问题。。可以看提交记录这些代码9年前都有了。。。

太难了

后来配合官方文档看,也能大概明白:

<resultMap id="detailedBlogResultMap" type="Blog">
    <constructor>
      <idArg column="blog_id" javaType="int"/>
    </constructor>
    <result property="title" column="blog_title"/>
    <association property="author" javaType="Author">
      <id property="id" column="author_id"/>
      <result property="username" column="author_username"/>
      <result property="password" column="author_password"/>
      <result property="email" column="author_email"/>
      <result property="bio" column="author_bio"/>
      <result property="favouriteSection" column="author_favourite_section"/>
    </association>
    <collection property="posts" ofType="Post">
      <id property="id" column="post_id"/>
      <result property="subject" column="post_subject"/>
      <association property="author" javaType="Author"/>
      <collection property="comments" ofType="Comment">
        <id property="id" column="comment_id"/>
      </collection>
      <collection property="tags" ofType="Tag" >
        <id property="id" column="tag_id"/>
      </collection>
      <discriminator javaType="int" column="draft">
        <case value="1" resultType="DraftPost"/>
      </discriminator>
    </collection>
  </resultMap>

可以发现resultMap中可以嵌套

而中又可以嵌套其他的,比如 \

那如果要完整的解析这些嵌套的东西,最好的办法就是递归,

这就是为什么前面会有3中type的获取,因为这个方法不仅仅是在解析,还会被递归调用来解析 \ 等等。

先跳过其他的代码,我们先看看解析其他子节点

XMLMapperBuilder###buildResultMappingFromContext()

private ResultMapping buildResultMappingFromContext(XNode context, Class<?> resultType, List<ResultFlag> flags) throws Exception {
    String property;
    //判断时候需要通过构造方法赋值
    if (flags.contains(ResultFlag.CONSTRUCTOR)) {
        property = context.getStringAttribute("name");
    } else {
       //获取属性 
        property = context.getStringAttribute("property");
    }
    //获取其他属性

    String column = context.getStringAttribute("column");
    String javaType = context.getStringAttribute("javaType");
    String jdbcType = context.getStringAttribute("jdbcType");
    String nestedSelect = context.getStringAttribute("select");
    String nestedResultMap = context.getStringAttribute("resultMap",
//处理嵌套的resultMap
                                                        processNestedResultMappings(context, Collections.emptyList(), resultType));
 //继续获取其他的属性                                                       
    String notNullColumn = context.getStringAttribute("notNullColumn");
    String columnPrefix = context.getStringAttribute("columnPrefix");
    String typeHandler = context.getStringAttribute("typeHandler");
    String resultSet = context.getStringAttribute("resultSet");
    String foreignColumn = context.getStringAttribute("foreignColumn");
    boolean lazy = "lazy".equals(context.getStringAttribute("fetchType", configuration.isLazyLoadingEnabled() ? "lazy" : "eager"));
    Class<?> javaTypeClass = resolveClass(javaType);
    Class<? extends TypeHandler<?>> typeHandlerClass = resolveClass(typeHandler);
    JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
    //将这些属性通过解析助手进行解析
    return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect, nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resultSet, foreignColumn, lazy);
}

这里最重要的便是处理嵌套的resultMap

一般来说,能被嵌套的元素有:

XMLMapperBuilder###processNestedResultMappings()

  private String processNestedResultMappings(XNode context, List<ResultMapping> resultMappings, Class<?> enclosingType) throws Exception {
    if ("association".equals(context.getName())
      || "collection".equals(context.getName())
      || "case".equals(context.getName())) {
      //先不解析动态SQL  
      if (context.getStringAttribute("select") == null) {
        //验证collection节点是否包含必须要元素
        //必须包含resultMap 和 javaType其中一个  
        validateCollection(context, enclosingType);
        //调用最开始的方法,进行递归解析  
        ResultMap resultMap = resultMapElement(context, resultMappings, enclosingType);
        return resultMap.getId();
      }
    }
    return null;
  }

这里就能能看出,已经开始递归调用了

接下来,我们以 / 为主体元素,再次分析一遍resultMapElement的源代码

XMLMapperBuilder###parameterMapElement()

private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings, Class<?> enclosingType) throws Exception {
    ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
    //对于resultMap 获取 type
    //对于collection 获取 ofType 或 javaType
    //对于association 获取 javaType
    String type = resultMapNode.getStringAttribute("type",
        resultMapNode.getStringAttribute("ofType",
            resultMapNode.getStringAttribute("resultType",
                resultMapNode.getStringAttribute("javaType"))));
    //加载此type,可以指定别名
    Class<?> typeClass = resolveClass(type);
    if (typeClass == null) {
      //这里enclosingType一般都是父节点的type 
      //比如<resultMap type="test">
      //       <collection/>
      //那么enclosingType便是test  
      //如果有些节点没有配置type,允许的情况下,可以直接使用父节点的type
      //例如:
      //    <discriminator javaType="int" column="draft">
      //        <case value="1" resultType="DraftPost"/>
      //    </discriminator>
      // case节点中就没有配置type  
      typeClass = inheritEnclosingType(resultMapNode, enclosingType);
    }
    Discriminator discriminator = null;
    List<ResultMapping> resultMappings = new ArrayList<>();
    //添加已经解析过的resultMap
    resultMappings.addAll(additionalResultMappings);
    //继续解析子节点
    List<XNode> resultChildren = resultMapNode.getChildren();
    for (XNode resultChild : resultChildren) {
      //处理构造方法  
      if ("constructor".equals(resultChild.getName())) {
        processConstructorElement(resultChild, typeClass, resultMappings);
      }
      //处理鉴定器  
       else if ("discriminator".equals(resultChild.getName())) {
        discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
      } else {
        //处理其他子节点   
        List<ResultFlag> flags = new ArrayList<>();
        if ("id".equals(resultChild.getName())) {
          flags.add(ResultFlag.ID);
        }
        resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
      }
    }
    //获取id
    String id = resultMapNode.getStringAttribute("id",
            resultMapNode.getValueBasedIdentifier());
    String extend = resultMapNode.getStringAttribute("extends");
    Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
    //构建resultMap
    ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
    try {
      //最后的解析  
      return resultMapResolver.resolve();
    } catch (IncompleteElementException  e) {
      configuration.addIncompleteResultMap(resultMapResolver);
      throw e;
    }
  }

接下来看其他细节:

当子节点解析完了以后,会将子节点添加到resultMapping中,然后再解析最大的resultMap

MapperBuilderAssiant###addResultMap()

//resultMapResolver.resolve();内部调用的此方法
public ResultMap addResultMap(
      String id,
      Class<?> type,
      String extend,
      Discriminator discriminator,
      List<ResultMapping> resultMappings,
      Boolean autoMapping) {
    //首先给当前标签加上命名空间前缀
    id = applyCurrentNamespace(id, false);
    //然后给继承的标签加上命名空间前缀
    //从这个当前前缀可以看出来,继承只能继承当前命名空间的元素
    extend = applyCurrentNamespace(extend, true);
    //判断是否有集成的属性
    if (extend != null) {
      //如果所继承的属性还没有解析,那么抛出指定异常,稍后再解析  
      if (!configuration.hasResultMap(extend)) {
        throw new IncompleteElementException("Could not find a parent resultmap with id '" + extend + "'");
      }
      //通过继承找到对应的resultMap  
      ResultMap resultMap = configuration.getResultMap(extend);
      //获取这个resultMap id 下对应的所有resultMap (包括嵌套resultMap)
      List<ResultMapping> extendedResultMappings = new ArrayList<>(resultMap.getResultMappings());
      //去除重复resultMap
      extendedResultMappings.removeAll(resultMappings);
      // Remove parent constructor if this resultMap declares a constructor.
      boolean declaresConstructor = false;
      //检查是否需要使用带参构造方法构造resultMap
      for (ResultMapping resultMapping : resultMappings) {
        if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) {
          declaresConstructor = true;
          break;
        }
      }
      //如果需要使用构造方法创键`resultMap type`,那么将此需要使用构造方法的元素从继承元素中删除  
      if (declaresConstructor) {
        extendedResultMappings.removeIf(resultMapping -> resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR));
      }
      resultMappings.addAll(extendedResultMappings);
    }
    //构造resultMap
    ResultMap resultMap = new ResultMap.Builder(configuration, id, type, resultMappings, autoMapping)
        .discriminator(discriminator)
        .build();
    configuration.addResultMap(resultMap);
    return resultMap;
  }

这几段代码看着实在累,第一是比较复杂,第二个就是本来想要实现的功能也比较复杂。

这里可以看见MyBatis仅仅是将嵌套的resultMap分解为几个reusltMap,然后放入list中,并且这里解析配置就真正的只是解析配置,没有做任何多余的事、

同时可以看出来,MyBatis的模块划分是非常好的,

XMLConfigBuilder->XMLMapperBuilder->MapperBuilderAssistant

XMLMapperBuilder只用负责读取配置文件,而将配置文件生成对像则交给MapperBuilderAssistant

看上面的代码,resultMap也只解析了静态部分能够解析的地方,而需要动态生成的则直接原封不动的放入了Configuration类中

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

MyBatis 源码解析MyBatis如何解析配置 ?(六) 的相关文章

  • CSS 水平居中

    1 若元素内容为文字时 元素设置text align center text align属性指定元素文本的水平对齐方式 center 把文本排列到中间 p 这里是文本内容 p 2 父子元素宽度固定 父元素设置text align cente

随机推荐

  • SQL实用功能手册

    SQL实用功能手册 SQL基础复习 SQL结构化查询语言 是一种访问和处理数据库的计算机语言 对数据库操作 对表操作 对数据进行CRUD操作 操作视图 存储过程 索引 环境基础操作 安装mysql 启动mysql 配置环境变量 检查mysq
  • 搞懂java类加载机制和类加载器

    搞懂java类加载机制和类加载器 类加载概述 一个类从被加载到虚拟机内存中开始 到卸载出内存为止 它的整个生命周期将会经历加载 验证 准备 解析 初始化 使用和卸载七个阶段 其中验证 准备 解析三个部分统称为连接 如下图所示 其中加载 验证
  • Android学习——Adapter适配器

    AdapterView 容器控件 其整体效果由每一个子元素内容决定 子元素的形式由Adapter决定 AdapterView的子视图对象 ListView 以垂直滑动列表形式显示一组数据 GridView 以网格形式显示一组数据 Spinn
  • GPUView的使用

    本文翻译自GPUView的开发者Matt的blog https graphics stanford edu mdfisher GPUView html GPUview可以在 https docs microsoft com en us wi
  • 10个经典的C语言面试基础算法及代码

    本文是码农网原创整理 转载请看清文末的转载要求 谢谢合作 算法是一个程序和软件的灵魂 作为一名优秀的程序员 只有对一些基础的算法有着全面的掌握 才会在设计程序和编写代码的过程中显得得心应手 本文是近百个C语言算法系列的第二篇 包括了经典的F
  • C语言(Head First C)-5_1:使用多个源文件:数据类型和使用头文件声明函数

    该系列文章系个人读书笔记及总结性内容 任何组织和个人不得转载进行商业活动 5 1 使用多个源文件 数据类型和头文件 大程序不等于大源文件 只有一个源文件的话 维护耗时且困难 如何把源文件分解为易于管理的小模块 然后合成一个大程序 正是本章的
  • Append-only及其使用

    Append only 维基百科 Append only 是计算机数据存储的一种属性 将新数据附加到存储中 但现有数据是不可变的 许多数据结构和数据库实现了不可变对象 有效地使它们的数据结构只能追加 实现仅追加数据结构有很多好处 例如确保数
  • android 播放raw音频文件格式,Android 使用mediaplayer播放res/raw文件夹中的音乐的实例...

    Android 使用mediaplayer播放res raw文件夹中的音乐的实例 1 在res文件夹中新建一个文件夹重命名为raw 并且将要播放的音乐放到raw文件夹里面 2 修改layout目录下的xml布局文件 添加3个按钮空间和一个文
  • 计算机硬盘启动设置方法,bios设置硬盘启动,详细教您bios设置硬盘启动操作步骤...

    我们在操作电脑的时候 会遇到要重装系统的情况 每当这种时候我们就会想到通过bios设置来进行系统的重装 所以今天小编就来重点给你们说说关于bios设置硬盘启动的操作步骤 重装系统是我们在使用电脑的时候会经常在遇到问题时采用的解决方法 但说到
  • spring boot 获取yaml配置的对象和数组对象

    一 获取对象 例子 yaml中的配置 permission method POST path auth login spring boot 中 在需要使用到该配置的controller或者service或者专门的配置类中 添加method和
  • 磁盘加新盘扩容

    注 如果有磁盘超过2T 如下调整 使用parted来对GPT磁盘操作 进入交互式模式 没有的话yum安装下 parted dev sdb 将MBR磁盘格式化为GPT parted mklabel gpt 到这就可以进行下面的了 先查看下新磁
  • QT UI不能索引控件

    原因 Qt程序使用的UI h文件并不是最新的UI文件 最新的ui h在bulid文件夹里面了 软件依旧使用旧的ui h文件 简单讲就是先要从 ui生成ui h然后再编译 所以界面未更新实际上是因为ui h这个文件没有更新导致的 解决办法 1
  • 论文阅读-(GLIP)Grounded Language-Image Pre-training (目标检测+定位)

    Paper Grounded Language Image Pre training Code https github com microsoft GLIP 简介 定位任务与图像检测任务非常类似 都是去图中找目标物体的位置 目标检测为给出
  • 好一场逗鹅冤:一瓶老干妈撬动BAT

    近日 号称 南山必胜客 的腾讯法务部将低调做酱料的老干妈送上了热搜 腾讯状告老干妈欠广告费 老干妈则称腾讯遭遇诈骗 双方各执一词 引得不明真相的吃瓜群众热闹围观 01 腾讯被骗 老干妈躺赚 6月29日 广东省深圳市南山区人民法院发布一则民事
  • QTcpSocket 发送数据心得

    遇到不会用的函数前 最好还是看看手册QAQ 今天居然吃了这个大亏 先交代一下背景 在做TCP客户端的发送数据功能 要和服务器程序进行TCP IP通信 且根据通信协议要发送数组或者结构体 并且数组的每一个位都是有效数据位 因此不能像大多数人一
  • 跳转小程序:wx-open-launch-weapp 注意事项,不显示按钮问题

    JSSDK参考文件 一 注意查看引入JS的版本 版本 版本 引用1 6 引用1 6 引用1 6 http res wx qq com open js jweixin 1 6 0 js 如果不引用1 6会出现什么情况 开放标签列表不显示 所以
  • 浅谈路由器工作原理

    路由器的作用是实现网络的三层通信 将二层网络互联形成一个三层网络 路由器工作内容 1 封装和解封装 网卡CU 数据帧 2 维护路由表 3 IP转发 也叫网络转发 三层转发 路由器接口特点 路由器接口和计算机网卡接口一样 都能封装和解封装数据
  • 【Python:Pycharm】mmSegmentation语义分割框架教程

    文章目录 一 MMSegmentation介绍 二 MMSegmentation基本框架 1 model设置 2 dataset设置 2 1 Dataset Class文件配置 2 2 Dataset Config文件配置 2 3 Tota
  • C语言实现贪吃蛇(详细版)

    一 需要掌握的知识 C语言基础语法 结构体 指针 链表
  • MyBatis 源码解析MyBatis如何解析配置 ?(六)

    XMLMapperBuilder parameterMapElement 代码比较长了 因为parameterMap 涉及到比较多的东西