mybatis的resultMap完美解析

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
简介: mybatis的resultMap完美解析

在 select 语句中查询得到的是一张二维表, 水平方向上看是一个个字段, 垂直方向上看是一条条记录。作为面向对象的语言, Java 中的的对象是根据类定义创建的。类之间的引用关系可以认为是嵌套的关系。

 

在 mybatis 中, resultMap 节点定义了结果集和结果对象(JavaBean)之间的映射规则。

 

本文主要讲解的是 resultMap 的解析。

 

两个基础类

 

在阅读本文之前, 最好能对这两个类有相应的理解。

 

1.1、列映射类ResultMapping

 

ResultMapping 对象记录了结果集中一列与队友JavaBean中一个属性的对应关系。

 

更多详情, 请参考:

https://www.cnblogs.com/homejim/p/9833863.html

 

1.2、结果集映射类ResultMap

 

ResultMap 对应的是结果集 中的一个结果集。其基本组成部分中, 含有 ResultMapping 对象。

其组成大致如下:

 

更多详情, 请参考:

https://www.cnblogs.com/homejim/p/9840373.html

 

解析

 

2.1、入口函数

 

resultMap 是 mapper.xml 文件下的, 因此其是解析 Mapper 的一个环节。

 

resultMapElements(context.evalNodes("/mapper/resultMap"));

 

解析, 由于是可以有多个的, 因此, context.evalNodes("/mapper/resultMap")返回的是一个 List。

 

private void resultMapElements(List<XNode> list) throws Exception {
    // 遍历, 解析
    for (XNode resultMapNode : list) {
      try {
        resultMapElement(resultMapNode);
      } catch (IncompleteElementException e) {
        // ignore, it will be retried
      }
    }
  }

 

2.2、整个过程就是 resultMapElement 这个函数。

 

其流程大体如下:

 

 

对应的代码

 

private ResultMap resultMapElement(XNode resultMapNode) throws Exception {
    return resultMapElement(resultMapNode, Collections.<ResultMapping> emptyList());
}
 
  /**
   * 处理 <resultMap> 节点, 将节点解析成 ResultMap 对象, 下面包含有 ResultMapping 对象组成的列表
   * @param resultMapNode resultMap 节点
   * @param additionalResultMappings 另外的 ResultMapping 列
   * @return ResultMap 对象
   * @throws Exception
   */
  private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception {
    ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
    // 获取 ID , 默认值会拼装所有父节点的 id 或 value 或 property
    String id = resultMapNode.getStringAttribute("id",
        resultMapNode.getValueBasedIdentifier());
    // 获取 type 属性, 表示结果集将被映射为 type 指定类型的对象
    String type = resultMapNode.getStringAttribute("type",
        resultMapNode.getStringAttribute("ofType",
            resultMapNode.getStringAttribute("resultType",
                resultMapNode.getStringAttribute("javaType"))));
    // 获取 extends 属性, 其表示结果集的继承
    String extend = resultMapNode.getStringAttribute("extends");
    // 自动映射属性。 将列名自动映射为属性
    Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
    // 解析 type, 获取其类型
    Class<?> typeClass = resolveClass(type);
    Discriminator discriminator = null;
    // 记录解析的结果
    List<ResultMapping> resultMappings = new ArrayList<>();
    resultMappings.addAll(additionalResultMappings);
    // 处理子节点
    List<XNode> resultChildren = resultMapNode.getChildren();
    for (XNode resultChild : resultChildren) {
      // 处理 constructor 节点
      if ("constructor".equals(resultChild.getName())) {
        // 解析构造函数元素,其下的没每一个子节点都会生产一个 ResultMapping 对象
        processConstructorElement(resultChild, typeClass, resultMappings);
        // 处理 discriminator 节点
      } else if ("discriminator".equals(resultChild.getName())) {
        discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
        // 处理其余节点, 如 id, result, assosation d等
      } else {
        List<ResultFlag> flags = new ArrayList<>();
        if ("id".equals(resultChild.getName())) {
          flags.add(ResultFlag.ID);
        }
        // 创建 resultMapping 对象, 并添加到 resultMappings 中
        resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
      }
    }
    // 创建 ResultMapResolver 对象, 该对象可以生成 ResultMap 对象
    ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
    try {
      return resultMapResolver.resolve();
    } catch (IncompleteElementException  e) {
        // 如果无法创建 ResultMap 对象, 则将该结果添加到 incompleteResultMaps 集合中
      configuration.addIncompleteResultMap(resultMapResolver);
      throw e;
    }
  }

 

2.3、获取 id

 

id 对于 resultMap 来说是很重要的, 它是一个身份标识。具有唯一性

 

// 获取 ID , 默认值会拼装所有父节点的 id 或 value 或 property。
    String id = resultMapNode.getStringAttribute("id", resultMapNode.getValueBasedIdentifier());

 

这里涉及到 XNode 对象中的两个函数

 

  public String getStringAttribute(String name, String def) {
    String value = attributes.getProperty(name);
    if (value == null) {
      return def;
    } else {
      return value;
    }
  }

 

该函数是获取 XNode 对象对应 XML 节点的 name 属性值, 如果该属性不存在, 则返回传入的默认值 def。

 

而在获取 id 的过程中, 默认值是下面这个函数

 

  /**
   * 生成元素节点的基础 id
   * @return
   */
  public String getValueBasedIdentifier() {
    StringBuilder builder = new StringBuilder();
    XNode current = this;
    // 当前的节点不为空
    while (current != null) {
      // 如果节点不等于 this, 则在0之前插入 _ 符号, 因为是不断的获取父节点的, 因此是插在前面
      if (current != this) {
        builder.insert(0, "_");
      }
      // 获取 id, id不存在则获取value, value不存在则获取 property。
      String value = current.getStringAttribute("id",
          current.getStringAttribute("value",
              current.getStringAttribute("property", null)));
      // value 非空, 则将.替换为_, 并将value的值加上 []
      if (value != null) {
        value = value.replace('.', '_');
        builder.insert(0, "]");
        builder.insert(0,
            value);
        builder.insert(0, "[");
      }
      // 不管 value 是否存在, 前面都添加上节点的名称
      builder.insert(0, current.getName());
      // 获取父节点
      current = current.getParent();
    }
    return builder.toString();
  }

 

该函数是生成元素节点的id, 如果是这样子的 XML。

 

<employee id="${id_var}">
  <blah something="that"/>
  <first_name>Jim</first_name>
  <last_name>Smith</last_name>
  <birth_date>
    <year>1970</year>
    <month>6</month>
    <day>15</day>
  </birth_date>
  <height units="ft">5.8</height>
  <weight units="lbs">200</weight>
  <active>true</active>
</employee>

 

我们调用

 

 XNode node = parser.evalNode("/employee/height");
 node.getValueBasedIdentifier();

 

则, 返回值应该是

 

employee[${id_var}]_height

 

2.4、解析结果集的类型

 

结果集的类型, 对应的是一个 JavaBean 对象。通过反射来获得该类型。

 

    // 获取type, type 不存在则获取 ofType, ofType 
    // 不存在则获取 resultType, resultType 不存在则获取 javaType
    String type = resultMapNode.getStringAttribute("type",
        resultMapNode.getStringAttribute("ofType",
            resultMapNode.getStringAttribute("resultType",
                resultMapNode.getStringAttribute("javaType"))));
    // ... ...
    // 获取 type 对应的 Class 对象
    Class<?> typeClass = resolveClass(type);

 

看源码, 有很多个 def 值, 也就是说, 我们在配置结果集的类型的时候都是有优先级的。但是, 这里有一个奇怪的地方, 我源代码版本(3.5.0-SNAPSHOT)的 的属性, 只有 type, 没有 ofType/resultType/javaType。以下为相应的 DTD 约束:

 

<!ELEMENT resultMap (constructor?,id*,result*,association*,collection*, discriminator?)>
<!ATTLIST resultMap
id CDATA #REQUIRED
type CDATA #REQUIRED
extends CDATA #IMPLIED
autoMapping (true|false) #IMPLIED>

 

我怀疑是兼容以前的版本。

 

2.5、获取继承结果集和自动映射

 

    String extend = resultMapNode.getStringAttribute("extends");
    Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");

 

这个两个属性都是在配置 XML 的时候可有可无的。

 

2.6、解析

 

先看 DTD 约束

 

<!ELEMENT resultMap (constructor?,id*,result*,association*,collection*, discriminator?)>

 

可以有以下几个子节点:

 

 

子节点解析过程很简单

 

 

根据类型进行解析, 最后获得 resultMappings

 

   // 创建一个 resultMappings 的链表
    List<ResultMapping> resultMappings = new ArrayList<>();
    // 将从其他地方传入的additionalResultMappings添加到该链表中
    resultMappings.addAll(additionalResultMappings);
    // 获取子节点
    List<XNode> resultChildren = resultMapNode.getChildren();
    // 遍历解析子节点
    for (XNode resultChild : resultChildren) {
      if ("constructor".equals(resultChild.getName())) {
        // 解析构造函数元素,其下的没每一个子节点都会生产一个 ResultMapping 对象
        processConstructorElement(resultChild, typeClass, resultMappings);
      } else if ("discriminator".equals(resultChild.getName())) {
        // 解析 discriminator 节点
        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));
      }
    }

 

除了 discriminator 节点, 其余节点最后都会回到 buildResultMappingFromContext 方法上, 该方法是创建 ResultMapping 对象。

 

  /**
   * 获取一行, 如result等, 取得他们所有的属性, 通过这些属性建立 `ResultMapping` 对象
   * @param context 对于节点本身
   * @param resultType resultMap 的结果类型
   * @param flags flag 属性, 对应 ResultFlag 枚举中的属性。 一般情况下为空
   * @return 返回 ResultMapping
   * @throws Exception
   */
  private ResultMapping buildResultMappingFromContext(XNode context, Class<?> resultType, List<ResultFlag> flags) throws Exception {
    String property;
    // 获取节点的属性, 如果节点是构造函数(只有name属性, 没有property),
    // 则获取的是 name, 否则获取 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",
        processNestedResultMappings(context, Collections.<ResultMapping> emptyList()));
    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"));
 
    // 以上获取各个属性节点
    // 解析 javaType, typeHandler, jdbcType
    Class<?> javaTypeClass = resolveClass(javaType);
    @SuppressWarnings("unchecked")
    Class<? extends TypeHandler<?>> typeHandlerClass = (Class<? extends TypeHandler<?>>) resolveClass(typeHandler);
    JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
    // 创建resultMapping对象
    return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect, nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resultSet, foreignColumn, lazy);
  }

 

如果是 discriminator, 则处理该元素并创建鉴别器。

 

  /**
   * 处理鉴别器
   * @param context 节点
   * @param resultType 结果类型
   * @param resultMappings 列结果集合
   * @return 鉴别器
   * @throws Exception
   */
  private Discriminator processDiscriminatorElement(XNode context, Class<?> resultType, List<ResultMapping> resultMappings) throws Exception {
    String column = context.getStringAttribute("column");
    String javaType = context.getStringAttribute("javaType");
    String jdbcType = context.getStringAttribute("jdbcType");
    String typeHandler = context.getStringAttribute("typeHandler");
    // 先获取各个属性
    // 取得 javaType 对应的类型
    Class<?> javaTypeClass = resolveClass(javaType);
    // 取得 typeHandler 对应的类型
    @SuppressWarnings("unchecked")
    Class<? extends TypeHandler<?>> typeHandlerClass = (Class<? extends TypeHandler<?>>) resolveClass(typeHandler);
    // 取得 jdbcType 对应的类型
    JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
    // 创建 discriminatorMap, 并遍历子节点, 以 value->resultMap 的方式放入discriminatorMap中
    Map<String, String> discriminatorMap = new HashMap<>();
    for (XNode caseChild : context.getChildren()) {
      String value = caseChild.getStringAttribute("value");
      String resultMap = caseChild.getStringAttribute("resultMap", processNestedResultMappings(caseChild, resultMappings));
      discriminatorMap.put(value, resultMap);
    }
    // 创建鉴别器
    return builderAssistant.buildDiscriminator(resultType, column, javaTypeClass, jdbcTypeEnum, typeHandlerClass, discriminatorMap);
  }

 

鉴别器内部, 也是含有 ResultMapping 的

 

public class Discriminator {
 
  private ResultMapping resultMapping;
  private Map<String, String> discriminatorMap;
  ......
}

 

2.7、创建 ResultMap 对象

 

在解析完 的各个属性和子节点之后。创建 ResultMapResolver 对象, 通过对象可以生成 ResultMap。

 

/**
   * 创建并添加 ResultMap 到 Configuration 对象中
   * @param id id, 配置了 id 可以提高效率
   * @param type 类型
   * @param extend 继承
   * @param discriminator 鉴别器
   * @param resultMappings 列集
   * @param autoMapping 是否自动映射
   * @return 返回创建的 ResultMap 对象
   */
  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 + "'");
      }
      // 从 configuration 中获取继承的结果集
      ResultMap resultMap = configuration.getResultMap(extend);
      // 获取所集成结果集的所有 ResultMapping 集合
      List<ResultMapping> extendedResultMappings = new ArrayList<>(resultMap.getResultMappings());
      // 移除需要覆盖的 ResultMapping 集合
      extendedResultMappings.removeAll(resultMappings);
      // 如果该 resultMap 中定义了构造节点, 则移除其父节点的构造器
      boolean declaresConstructor = false;
      for (ResultMapping resultMapping : resultMappings) {
        if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) {
          declaresConstructor = true;
          break;
        }
      }
      if (declaresConstructor) {
        Iterator<ResultMapping> extendedResultMappingsIter = extendedResultMappings.iterator();
        while (extendedResultMappingsIter.hasNext()) {
          if (extendedResultMappingsIter.next().getFlags().contains(ResultFlag.CONSTRUCTOR)) {
            extendedResultMappingsIter.remove();
          }
        }
      }
      // 添加需要被继承的 ResultMapping 集合
      resultMappings.addAll(extendedResultMappings);
    }
    // 通过建造者模式创建 ResultMap 对象
    ResultMap resultMap = new ResultMap.Builder(configuration, id, type, resultMappings, autoMapping)
        .discriminator(discriminator)
        .build();
    // 添加到 Configuration 对象中
    configuration.addResultMap(resultMap);
    return resultMap;
  }

 

解析结果

 

有如下的数据库表

 

 

通过代码生成器生成 XML 和 Mapper。

 

添加结果集

 

 

对应的 sql

 

 

则最后解析出的结果

 

 

使用示例

https://github.com/homejim/mybatis-cn

相关文章
|
27天前
|
SQL Java 数据库连接
canal-starter 监听解析 storeValue 不一样,同样的sql 一个在mybatis执行 一个在数据库操作,导致解析不出正确对象
canal-starter 监听解析 storeValue 不一样,同样的sql 一个在mybatis执行 一个在数据库操作,导致解析不出正确对象
|
3月前
|
SQL XML Java
mybatis复习02,简单的增删改查,@Param注解多个参数,resultType与resultMap的区别,#{}预编译参数
文章介绍了MyBatis的简单增删改查操作,包括创建数据表、实体类、配置文件、Mapper接口及其XML文件,并解释了`#{}`预编译参数和`@Param`注解的使用。同时,还涵盖了resultType与resultMap的区别,并提供了完整的代码实例和测试用例。
mybatis复习02,简单的增删改查,@Param注解多个参数,resultType与resultMap的区别,#{}预编译参数
|
4月前
|
安全 Java 数据库连接
后端框架的学习----mybatis框架(3、配置解析)
这篇文章详细介绍了MyBatis框架的核心配置文件解析,包括环境配置、属性配置、类型别名设置、映射器注册以及SqlSessionFactory和SqlSession的生命周期和作用域管理。
后端框架的学习----mybatis框架(3、配置解析)
|
6月前
|
Java 关系型数据库 数据库连接
【MyBatis】初步解析MyBatis:实现数据库交互与关系映射的全面指南
【MyBatis】初步解析MyBatis:实现数据库交互与关系映射的全面指南
466 1
|
6月前
|
SQL Java 数据库连接
MyBatis插件深度解析:功能、原理、使用、应用场景与最佳实践
MyBatis插件深度解析:功能、原理、使用、应用场景与最佳实践
|
6月前
|
SQL Java 数据库连接
Mybatis日志SQL解析
Mybatis日志SQL解析
|
6月前
|
SQL Java 数据库连接
【MyBatis】深入解析MyBatis:高效操作数据库技术详解
【MyBatis】深入解析MyBatis:高效操作数据库技术详解
47 0
|
7月前
|
SQL 缓存 Java
|
1月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
73 2
|
2月前
|
缓存 Java 程序员
Map - LinkedHashSet&Map源码解析
Map - LinkedHashSet&Map源码解析
78 0

推荐镜像

更多