Mybatis Mapper.xml 配置文件中 resultMap 节点的源码解析

本人花费半年的时间总结的《Java面试指南》已拿腾讯等大厂offer,已开源在github ,欢迎star!

本文GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收录,这是我花了6个月总结的一线大厂Java面试总结,本人已拿大厂offer,欢迎star

原文链接:blog.ouyangsihai.cn >> Mybatis Mapper.xml 配置文件中 resultMap 节点的源码解析

相关文章

前言

在上篇文章  介绍了 Maper.xml 配置文件的解析,但是没有解析 resultMap 节点,因为该解析比较复杂,也比较难理解,所有单独拿出来进行解析。

在使用 Mybatis 的时候,都会使用 resultMap节点来绑定列与 bean属性的对应关系,但是一般就只会使用其简单的属性,他还有一些比较复杂的属性可以实现一些高级的功能,在没查看源码之前,我也只会简单的使用,很多高级的用法都没有使用过,通过这次学习,希望能在工作使用,能够写出简洁高效的SQL。

resultMap的定义

先来看看 resultMap 节点的官方定义:

简单的使用:


resultMap id="userResultMap" type="User"
  id property="id" column="user_id" /
  result property="username" column="user_name"/
  result property="password" column="user_password"/
/resultMap

会把列名和属性名进行绑定,该节点一共有 4 个属性:

  • `**id**` :表示该 resultMap,供其他的语句调用
  • `**type**`:表示其对应的pojo类型,可以使用别名,也可以使用全限定类名
  • `**autoMapping**`:如果设置这个属性,MyBatis 将会为这个 ResultMap开启或者关闭自动映射。这个属性会覆盖全局的属性 `autoMappingBehavior`。默认值为:`unset`。
  • `**extends**`:继承,一个 `resultMap` 可以继承另一个 `resultMap`,这个属性是不是没有用过 ? ^^
  • **type**:表示其对应的pojo类型,可以使用别名,也可以使用全限定类名

    **extends**:继承,一个 resultMap 可以继承另一个 resultMap,这个属性是不是没有用过 ? ^^

    接下来看下它可以有哪些子节点:

  • `**constructor**` - 用于注入结果到构造方法中
  • `**id**` – 标识ID列
  • `**result**` – 表示一般列
  • `**association**` – 关联查询
  • `**collection**` – 查询集合
  • `**discriminator**` - 鉴别器:mybatis可以使用discriminator判断某列的值,然后根据某列的值改变封装行为
  • **id** – 标识ID列

    **association** – 关联查询

    **discriminator** - 鉴别器:mybatis可以使用discriminator判断某列的值,然后根据某列的值改变封装行为

    constructor

    在查询数据库得到数据后,会把对应列的值赋值给 javabean 对象对应的属性,默认情况下 mybatis 会调用实体类的无参构造方法创建一个实体类,然后再给各个属性赋值,如果没有构造方法的时候,可以使用 constructor 节点进行绑定,如现有如下的构造方法:

    
     public Person(int id, String name, String job, int age) {
            this.id = id;
            this.name = name;
            this.job = job;
            this.age = age;
        }
    

    则,可以使用 constructor  节点进行绑定:

    
        resultMap id="queryPersonMap" type="mybatis.pojo.Person" 
            constructor
                idArg column="id" javaType="int"/
                arg column="name" javaType="string" /
                arg column="job" javaType="string" /
                arg column="age" javaType="int" /
            /constructor
        /resultMap
    

    association

    关联查询,在级联中有一对一、一对多、多对多等关系, association主要是用来解决一对一关系的, association 可以有多种使用方式:

    比如现在有一个 Person 类,它有一个 address 属性,关联 Address 对象:

    
    public class Person implements Serializable {
    
        private int id;
    
        private String name;
    
        private String job;
    
        private int age;
    
        private Address address;
    }
    
    public class Address {
        private int id;
    
        private String name;
    
        private long number;
    
    }
    

    关联查询方式一:

    
        resultMap id="queryPersonMap" type="mybatis.pojo.Person" 
            id column="id" property="id"/
            result column="name" property="name" /
            result column="job" property="job" /
            result column="age" property="age"/
            association property="address" column="address_id" javaType="mybatis.pojo.Address" select="queryAddress" /
        /resultMap
    
        select id="queryAddress" resultType="mybatis.pojo.Address"
            select * from address where id = #{id}
        /select
    

    关联查询方式二:

    
        resultMap id="queryPersonMap" type="mybatis.pojo.Person" 
            id column="id" property="id"/
            result column="name" property="name" /
            result column="job" property="job" /
            result column="age" property="age"/
            association property="address" column="address_id" javaType="mybatis.pojo.Address" resultMap="addressMap"/
        /resultMap
    
        resultMap id="addressMap" type="mybatis.pojo.Address"
            id column="id" property="id"/
            result column="name" property="name"/
            result column="number" property="number"/
        /resultMap
    

    关联查询方式三:

    
        resultMap id="queryPersonMap" type="mybatis.pojo.Person" 
            id column="id" property="id"/
            result column="name" property="name" /
            result column="job" property="job" /
            result column="age" property="age"/
            association property="address" javaType="mybatis.pojo.Address"
                id column="id" property="id"/
                result column="name" property="name"/
                result column="number" property="number"/
            /association
        /resultMap
    

    collection

    collection 集合,如果pojo对象有一个属性是集合类型的,可以使用 collection 来进行查询:

    
    public class Person implements Serializable {
    
        private int id;
    
        private String name;
    
        private String job;
    
        private int age;
    
        private ListAddress addressList;
    }
    

    collection 使用如下:

    
        resultMap id="queryPersonMap" type="mybatis.pojo.Person" 
            id column="id" property="id"/
            result column="name" property="name" /
            result column="job" property="job" /
            result column="age" property="age"/
            collection property="addressList" javaType="ArrayList" ofType="mybatis.pojo.Address"
                id column="id" property="id"/
                result column="name" property="name"/
                result column="number" property="number"/
            /collection
        /resultMap
    

    当然还有其他的方法,具体可以参考官网。

    discriminator

    鉴别器,mybatis可以使用 discriminator判断某列的值,然后根据某列的值改变封装行为,有点像 Java的 switch 语句,鉴别器指定了 column javaType 属性。 列是 MyBatis 查找比较值的地方。 JavaType 是需要被用来保证等价测试的合适类型,

    比如某列的值等于多少,则返回1,等于多少返回2等等。

    
    resultMap id="vehicleResult" type="Vehicle"
      id property="id" column="id" /
      result property="vin" column="vin"/
      result property="year" column="year"/
      result property="make" column="make"/
      result property="model" column="model"/
      result property="color" column="color"/
      discriminator javaType="int" column="vehicle_type"
        case value="1" resultMap="carResult"/
        case value="2" resultMap="truckResult"/
        case value="3" resultMap="vanResult"/
        case value="4" resultMap="suvResult"/
      /discriminator
    /resultMap
    

    以上就是 resultMap 节点的全部使用方法,下面是一个比较复杂的例子,源码解析会按照其来解析,例子来自于官方文档。

    
    resultMap id="detailedBlogResultMap" type="Blog"
      constructor
        idArg column="blog_id" javaType="int"/
        arg column="name" javaType="string" /
      /constructor
    
      id column="id" property="id" /
      result property="title" column="blog_title"/
    
      association property="author" javaType="Author"
        id property="id" column="author_id"/
        result property="username" column="author_username"/
      /association
    
      collection property="posts" ofType="Post"
        id property="id" column="post_id"/
        result property="subject" column="post_subject"/
      /collection
    
      discriminator javaType="int" column="draft"
        case value="1" resultType="DraftPost"/
      /discriminator
    /resultMap
    

    resultMap 源码解析

    首先需要说明的是,一个 resultMap 节点会解析成一个 ResultMap 对象,而每个子节点(除了discriminator节点)会被解析成 ResultMapping 对象,即一个 ResultMap 包含的是 ResultMapping 对象的集合。

    先来看看 ResultMapping 的一个声明:

    
    public class ResultMapping {
      // configuration 对象
      private Configuration configuration;
      private String property;
      private String column;
      private Class? javaType;
      private JdbcType jdbcType;
      private TypeHandler? typeHandler;
      // 对应的是 resultMap 属性,通过id来引用其他的resultMap
      private String nestedResultMapId;
      // 对应的是 select 属性,通过id来引用其他的select节点的定义
      private String nestedQueryId;
      private SetString notNullColumns;
      private String columnPrefix;
      // 处理后的标志,标志有两个 id和constructor
      private ListResultFlag flags;
      // 对应节点的column属性拆分后生成的结果,composites.size()0会使column为null
      private ListResultMapping composites;
      private String resultSet;
      private String foreignColumn;
      private boolean lazy;
    }
    

    ResultMap 的声明如下:

    
    public class ResultMap {
      // ID,表示一个resultMap
      private String id;
      // 该resultMap对应的Javabean类型
      private Class? type;
      // 对应的是除了discriminator节点外的其他节点
      private ListResultMapping resultMappings;
      // id 节点的映射集合
      private ListResultMapping idResultMappings;
      // 构造节点的集合
      private ListResultMapping constructorResultMappings;
      // 记录了映射关系中 不带有contructot节点的的映射关系
      private ListResultMapping propertyResultMappings;
      // column集合
      private SetString mappedColumns;
      // discriminator 节点
      private Discriminator discriminator;
      private boolean hasNestedResultMaps;
      private boolean hasNestedQueries;
      private Boolean autoMapping;
    }
    

    解析:

    
     resultMapElements(context.evalNodes("/mapper/resultMap"));
    
      private void resultMapElements(ListXNode list) throws Exception {
        for (XNode resultMapNode : list) {
          try {
            // 解析每个 resultMap 节点
            resultMapElement(resultMapNode);
          } catch (IncompleteElementException e) {
            // ignore, it will be retried
          }
        }
      }
    
      private ResultMap resultMapElement(XNode resultMapNode) throws Exception {
        // 注意这里传入的是一个空的集合
        return resultMapElement(resultMapNode, Collections.ResultMapping emptyList());
      }
    

    主要的解析方法:

    
      private ResultMap resultMapElement(XNode resultMapNode, ListResultMapping additionalResultMappings) throws Exception {
        ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
        // ID 属性
        String id = resultMapNode.getStringAttribute("id", resultMapNode.getValueBasedIdentifier());
        // type属性
        String type = resultMapNode.getStringAttribute("type",resultMapNode.getStringAttribute("ofType",        resultMapNode.getStringAttribute("resultType",resultMapNode.getStringAttribute("javaType"))));
        // extends 属性
        String extend = resultMapNode.getStringAttribute("extends");
        // autoMapping 属性
        Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
        // 从注册的类型管理器里面查找对应的类型
        Class? typeClass = resolveClass(type);
        // discriminator 节点
        Discriminator discriminator = null;
        ListResultMapping resultMappings = new ArrayListResultMapping();
        resultMappings.addAll(additionalResultMappings);
        // 处理子节点
        ListXNode resultChildren = resultMapNode.getChildren();
        for (XNode resultChild : resultChildren) {
          if ("constructor".equals(resultChild.getName())) {
            // 处理 constructor 节点
            processConstructorElement(resultChild, typeClass, resultMappings);
          } else if ("discriminator".equals(resultChild.getName())) {
            // 处理discriminator节点
            discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
          } else {
            ListResultFlag flags = new ArrayListResultFlag();
            if ("id".equals(resultChild.getName())) {
              flags.add(ResultFlag.ID);
            }
            // 处理其他节点,创建 resultMapping 对象并添加到集合中
            resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
          }
        }
        ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
        try {
          // 创建代表该 resultMap 节点的 ResultMap 对象并添加到 ResultMap 集合中。
          return resultMapResolver.resolve();
        } catch (IncompleteElementException  e) {
          // 解析失败,添加到集合,重新解析
          configuration.addIncompleteResultMap(resultMapResolver);
          throw e;
        }
      }
    

    处理 constructor 节点:

    
      private void processConstructorElement(XNode resultChild, Class? resultType, ListResultMapping resultMappings) throws Exception {
        ListXNode argChildren = resultChild.getChildren();
        for (XNode argChild : argChildren) {
          ListResultFlag flags = new ArrayListResultFlag();
          // 向集合中添加 contrucator 标志
          flags.add(ResultFlag.CONSTRUCTOR);
          if ("idArg".equals(argChild.getName())) {
            // 添加id标志
            flags.add(ResultFlag.ID);
          }
          // 创建 ResultMapping 对象并添加到集合中
          resultMappings.add(buildResultMappingFromContext(argChild, resultType, flags));
        }
      }
    

    创建 ResultMapping 对象:

    
      private ResultMapping buildResultMappingFromContext(XNode context, Class? resultType, ListResultFlag flags) throws Exception {
        // 解析节点的属性
        String 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"));
        Class? javaTypeClass = resolveClass(javaType);
        @SuppressWarnings("unchecked")
        // 对应的 typeHandler 类型
        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);
      }
    

    之后是创建 ResultMapped 对象并添加到集合中:

    
    ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
    // 调用的使用 builderAssistant 的 addResultMap 方法
    return resultMapResolver.resolve();
    
    
      public ResultMap addResultMap(String id, Class? type, String extend, Discriminator discriminator, ListResultMapping resultMappings, Boolean autoMapping) {
        // 为 id 加上 namespace即 namespace.id
        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);
          ListResultMapping extendedResultMappings = new ArrayListResultMapping(resultMap.getResultMappings());
          // 因为上面添加过一次,现在要删除重复的
          extendedResultMappings.removeAll(resultMappings);
          // Remove parent constructor if this resultMap declares a constructor.
          boolean declaresConstructor = false;
          for (ResultMapping resultMapping : resultMappings) {
            if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) {
              declaresConstructor = true;
              break;
            }
          }
          if (declaresConstructor) {
            IteratorResultMapping extendedResultMappingsIter = extendedResultMappings.iterator();
            while (extendedResultMappingsIter.hasNext()) {
              if (extendedResultMappingsIter.next().getFlags().contains(ResultFlag.CONSTRUCTOR)) {
                extendedResultMappingsIter.remove();
              }
            }
          }
          resultMappings.addAll(extendedResultMappings);
        }
        // 创建 resultMap 
        ResultMap resultMap = new ResultMap.Builder(configuration, id, type, resultMappings, autoMapping)
            .discriminator(discriminator)
            .build();
        // 添加到集合
        configuration.addResultMap(resultMap);
        return resultMap;
      }
    

    到这里,就把 resultMap 节点解析完毕了,之后在解析 Mapper.xml 文件的其他节点,参考 

    原文始发于微信公众号(Java技术大杂烩):

    本人花费半年的时间总结的《Java面试指南》已拿腾讯等大厂offer,已开源在github ,欢迎star!

    本文GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收录,这是我花了6个月总结的一线大厂Java面试总结,本人已拿大厂offer,欢迎star

    原文链接:blog.ouyangsihai.cn >> Mybatis Mapper.xml 配置文件中 resultMap 节点的源码解析


     上一篇
    Mybatis 解析 SQL 源码分析一 Mybatis 解析 SQL 源码分析一
    相关文章 前言在使用 Mybatis 的时候,我们在 Mapper.xml 配置文件中书写 SQL;文件中还配置了对应的 dao, SQL 中还可以使用一些诸如 for循环, if判断之类的高级特性,当数据库列和 JavaBean属性
    2021-04-05
    下一篇 
    Mybatis 解析 SQL 源码分析二 Mybatis 解析 SQL 源码分析二
    前言在上两篇文章   和    中分析了 Mybatis 是如何解析 Mapper.xml 配置文件的,配置文件中配置的 SQL 节点被解析成了一个个的 MappedStatement 对象放到了全局的配置对象 Configuration
    2021-04-05