mybatis-configuration容器(3)--结果映射器--resultMap

        前面分享到第二个容器typeHandlerRegistry时中间穿插了hashmap的解析.现在回来继续mybatis系列.这一节分享mybatis结果映射的最重要的一个结构ResultMap结构.

        前一节分享到typeHandler是用来映射某一个mysql列与java属性映射的处理器.我们的查询语句往往都会查询出很多列,需要把很多列映射到不同的java对象中去.记录mysql列与java属性映射的数据结构被mybatis定义为ResultMap.

        mybatis解析ResultMap结构流程图如下:
resultMap解析流程图
        具体构造器标签、鉴别器标签、常规标签是处理方法调用是怎样的呢?见下面的流程图:
resultMap方法调用
        好的,流程图就介绍到这里.接下来我们通过源码来分析下ResultMap的解析过程,以及解析后在内存中的存储结构.

1.源码解读

1.1.ResultMap总入口

resultMapElement该方法主要作用有:

  • 从ResultMap的根节点(包含嵌套的根节点)出发,收集所有子节点生成的ResultMapping结构.
  • 并构建ResultMap自身的属性,比如autoMapping是否开启属性字段映射、嵌套ResultMap的id生成等.
  • 将ResultMap结构存放到Configuration的resultMaps中.
// XMLMapperBuilder
private void configurationElement(XNode context) {
	// 省略...
    // 解析mappper.xml中的所有resultMap标签
    resultMapElements(context.evalNodes("/mapper/resultMap"));
    // 省略...
}

private void resultMapElements(List<XNode> list) {
   // 省略... 
   resultMapElement(resultMapNode);
   // 省略...
}

private ResultMap resultMapElement(XNode resultMapNode) {
    return resultMapElement(resultMapNode, Collections.emptyList(), null);
}

private ResultMap resultMapElement(XNode resultMapNode, 
                                   List<ResultMapping> additionalResultMappings, 
                                   Class<?> enclosingType) {
    ErrorContext.instance().activity("processing " 
               + resultMapNode.getValueBasedIdentifier());
    // type是resultMap标签的
    // ofType是collection标签的
    // resultType是select,insert,update,delete标签的
    // javaType是id,result,association,collection标签的
    // 其他的标签只有type属性,只有collection标签既有ofType又有javaType,
    // 而且ofType又在前面,所以
    // collection标签如果配置了ofType,那么他的javaType就不生效了.
    // 感觉collection标签的javaType和ofType
    // 属性效果一样,不要同时写会被覆盖
    // 1.获取该标签对应的javaType(对应的java类型)
    // 从这里能看出,如果一个标签允许配置多个type,那么优先级为 
    // 【type】>【ofType】>【resultType】>【javaType】
    String type = resultMapNode
                        .getStringAttribute("type",
                            resultMapNode.getStringAttribute("ofType",
                               resultMapNode.getStringAttribute("resultType",
                                  resultMapNode.getStringAttribute("javaType"))));
    // 这里是根据type获取Class.不太清楚的同学可以参照第一篇文章typeAliasRegistry
    Class<?> typeClass = resolveClass(type);
    // 2.如果该标签没有javaType,那么看看该标签的父标签是否有type
    //    (1) 如果是association标签,且该节点上没有resultMap,
    //            那么这个Class就是对应property的类型
    //    (2) 如果是case标签,且该节点上没有resultMap,那么这个Class就是父节点的type类型
    if (typeClass == null) {
        typeClass = inheritEnclosingType(resultMapNode, enclosingType);
    }
    Discriminator discriminator = null;
    List<ResultMapping> resultMappings = new ArrayList<>(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));
        }
    }
    // 这时获取ResultMap的id,如果写了id属性,那么这个就是自己写的id;
    // 如果是嵌套resultMap是没有id属性的.
    // 嵌套resultMap的id由resultMapNode.getValueBasedIdentifier()
    // 方法推断生成
    String id = resultMapNode.getStringAttribute(
        "id",resultMapNode.getValueBasedIdentifier());
    // 可以继承其他的ResultMap
    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;
    }
}

public ResultMap resolve() {
    return assistant.addResultMap(this.id, this.type, this.extend, 
            this.discriminator, this.resultMappings, this.autoMapping);
}

// 3.存放到Configuration的resultMaps中
public ResultMap addResultMap(
                        String id,
                        Class<?> type,
                        String extend,
                        Discriminator discriminator,
                        List<ResultMapping> resultMappings,
                        Boolean autoMapping) {
    // ResultMap中的id属性值,会判断该id值是否添加了namespace,如果添加了则不加了
    // ,如果没添加,需要补全namespace
    id = applyCurrentNamespace(id, false);
    // 这个是获取继承的ResultMap的id,没继承的话则为null
    extend = applyCurrentNamespace(extend, true);

    // 这里是ResultMap中有extends属性的,如果ResultMap中没有extends属性不会进入.
    if (extend != null) {
        // 这里如果有嵌套的ResultMap,那么这个ResultMap应该在解析该
        // ResutMap的时候就已经把父RM给解析出来了,而且放到configuration中了
        if (!configuration.hasResultMap(extend)) {
            throw new IncompleteElementException(
                "Could not find a parent resultmap with id '" + extend + "'");
        }
        ResultMap resultMap = configuration.getResultMap(extend);
        List<ResultMapping> extendedResultMappings 
            = new ArrayList<>(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) {
            extendedResultMappings.removeIf(resultMapping -> 
                 resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR));
        }
        resultMappings.addAll(extendedResultMappings);
    }
    // 构建鉴别器
    ResultMap resultMap = new ResultMap
        .Builder(configuration, id, type, resultMappings, autoMapping)
        .discriminator(discriminator)
        .build();
    // 存入configuration
    configuration.addResultMap(resultMap);
    return resultMap;
}

// 嵌套resultMap结构的id生成
public String getValueBasedIdentifier() {
    StringBuilder builder = new StringBuilder();
    XNode current = this;
    // 这里是循环寻找parent,比如<collection>标签是<resultMap>标签的子,
    // 先组装ResultMap名称,然后在拼上父的名称
    // <resultMap>标签是<mapper>标签的子,也需要拼上父的名称
    while (current != null) {
        // 当前节点的不需要插入下划线"_".
        if (current != this) {
            builder.insert(0, "_");
        }
        // 有id取id,<resultMap>标签
        // 没有id取value,<case>标签
        // 没有id、value的取property标签,<collection>、<association>标签
        String value = current.getStringAttribute("id",
                        current.getStringAttribute("value",
                          urrent.getStringAttribute("property", (String) null)));
        if (value != null) {
            value = value.replace('.', '_');
            builder.insert(0, "]");
            builder.insert(0,value);
            builder.insert(0, "[");
        }
        builder.insert(0, current.getName());
        current = current.getParent();
    }
    return builder.toString();
}

接下来我们看看构造器和鉴别器是如何处理自己特有的属性呢吧.

1.2.构造器与鉴别器解析

        processConstructorElement:该方法的主要作用为解析构造函数标签.存入集合中.供后续判断使用.

// XMLMapperBuilder
private void processConstructorElement(XNode resultChild, 
                                       Class<?> resultType, 
                                       List<ResultMapping> resultMappings) {
    // 获取所有子节点
    List<XNode> argChildren = resultChild.getChildren();
    // 遍历constructor标签下的所有子标签,构造器可能有多个参数.对应子标签有idArg,arg
    for (XNode argChild : argChildren) {
        List<ResultFlag> flags = new ArrayList<>();
        flags.add(ResultFlag.CONSTRUCTOR);
        if ("idArg".equals(argChild.getName())) {
            flags.add(ResultFlag.ID);
        }
        resultMappings.add(
            buildResultMappingFromContext(argChild, resultType, flags));
    }
}

processDiscriminatorElement 该方法的主要作用有:

  • 解析鉴别器所属的列字段,还记得前一节讲的五个重要元素(property、column、javaType、jdbcType、typeHandler)
  • 构建mysql列返回的不同值选到不同ResultMap的关系映射.
  • 构建Discriminator,并将其作为父ResultMap结构的成员变量.
// XMLMapperBuilder
private Discriminator processDiscriminatorElement(XNode context, 
                    Class<?> resultType,List<ResultMapping> resultMappings) {
    String column = context.getStringAttribute("column");
    String javaType = context.getStringAttribute("javaType");
    String jdbcType = context.getStringAttribute("jdbcType");
    String typeHandler = context.getStringAttribute("typeHandler");
    Class<?> javaTypeClass = resolveClass(javaType);
    Class<? extends TypeHandler<?>> typeHandlerClass
        				= resolveClass(typeHandler);
    // typeHandler的推断可以我看前面的文章TypeHandlerRegistry
    JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
    Map<String, String> discriminatorMap = new HashMap<>();
    for (XNode caseChild : context.getChildren()) {
        // 这里面是<case>标签,value就是<case value="1">的value
        String value = caseChild.getStringAttribute("value");
        // resultMap就是<case>标签里面的标签,包括<association>、<collection>
        String resultMap = caseChild.getStringAttribute("resultMap", 
                   processNestedResultMappings(
                       caseChild, resultMappings, resultType));
        discriminatorMap.put(value, resultMap);
    }
    return builderAssistant.buildDiscriminator(
        resultType, column, javaTypeClass, 
        jdbcTypeEnum, typeHandlerClass, discriminatorMap);
}

1.3.普通字段

buildResultMappingFromContext

// XMLMapperBuilder
private ResultMapping buildResultMappingFromContext(XNode context, 
                    Class<?> resultType, List<ResultFlag> flags) {
    String property;
    // 如果是从构造方法的子节点进来的
    // <constructor> <idArg column="blog_id" javaType="int"/> </constructor>
    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");
    // 是否引用外部的resultMap,如果标签树何止了resultMap,
    // 那么嵌套里面的resultMap映射无效
    String nestedResultMap = context.getStringAttribute("resultMap", () ->
            processNestedResultMappings(
                context, Collections.emptyList(), resultType));
    // association
    String notNullColumn = context.getStringAttribute("notNullColumn");
    // association
    String columnPrefix = context.getStringAttribute("columnPrefix");

    // 列和字段对应关系
    String typeHandler = context.getStringAttribute("typeHandler");
    // association  多结果集,只有在返回两个结果集的时候才有用,
    // 一般用于存储过程返回两个结果集的场景,这里略过
    String resultSet = context.getStringAttribute("resultSet");
    // association  多结果集(略) 
    String foreignColumn = context.getStringAttribute("foreignColumn");
    // association  是否是懒加载
    boolean lazy = "lazy".equals(context.getStringAttribute("fetchType",
                configuration.isLazyLoadingEnabled() ? "lazy" : "eager"));
    // 创建对应的结果类型,好寻找对应的typeHandler做相应处理,
    // 比如id的javaType为int,那么这里返回的是Integer的Class对象
    // 这里如果没定义javaType,那么这里返回的是null
    Class<?> javaTypeClass = resolveClass(javaType);
    // 这里如果没定义typeHandler,那么这里返回的是null
    Class<? extends TypeHandler<?>> typeHandlerClass = resolveClass(typeHandler);
    // 这里如果没定义jdbcType,那么这里返回的也是null
    // typeHandler的推断可以我看前面的文章TypeHandlerRegistry
    JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
    return builderAssistant.buildResultMapping(
        resultType, property, column, javaTypeClass, jdbcTypeEnum, 
        nestedSelect, nestedResultMap, notNullColumn, columnPrefix, 
        typeHandlerClass, flags, resultSet, foreignColumn, lazy);
}

buildResultMapping

// MapperBuilderAssistant
public ResultMapping buildResultMapping(
      Class<?> resultType,
      String property,
      String column,
      Class<?> javaType,
      JdbcType jdbcType,
      String nestedSelect,
      String nestedResultMap,
      String notNullColumn,
      String columnPrefix,
      Class<? extends TypeHandler<?>> typeHandler,
      List<ResultFlag> flags,
      String resultSet,
      String foreignColumn,
      boolean lazy) {
    // 这里resultType为ResultMap的javaType,
    // property为ResultMap下的子标签对应的java字段名,
    // 这里是解析property的java类型
    // 1.推断该映射对应的javaType.根据该resultType类型通过property找到set方法,
    // 根据set方法的第一个字段的类型作为javaType.
    Class<?> javaTypeClass = resolveResultJavaType(resultType, property, javaType);
    // 这里是解析property字段对应的typeHandler,这里如果不指定java字段对应的typeHandler,
    // 那么这里返回null,后面用java反射获取.
    // 2.解析typeHandler
    // typeHandler的推断可以我看前面的文章TypeHandlerRegistry
    // 这里如果有写了typeHandler,那么这里会创建我们写的typeHandler对象,
    // 如果没有写那么这里TypeHandler返回值为null
    TypeHandler<?> typeHandlerInstance 
        = resolveTypeHandler(javaTypeClass, typeHandler);
    List<ResultMapping> composites;
    // 如果没有嵌套的select,且foreignColumn【这个是多数据集】为空
    if ((nestedSelect == null || nestedSelect.isEmpty()) 
        && (foreignColumn == null || foreignColumn.isEmpty())) {
      composites = Collections.emptyList();
    } else {
      composites = parseCompositeColumnName(column);
    }
    return new ResultMapping.Builder(configuration, property, column, javaTypeClass)
        .jdbcType(jdbcType)
        .nestedQueryId(
        	applyCurrentNamespace(nestedSelect, true)) // select 绑上当前的命名空间
        .nestedResultMapId(
        	applyCurrentNamespace(nestedResultMap, true)) // select 绑上当前的命名空间
        .resultSet(resultSet)
        .typeHandler(typeHandlerInstance)
        .flags(flags == null ? new ArrayList<>() : flags)
        .composites(composites)
        .notNullColumns(parseMultipleColumnNames(notNullColumn))
        .columnPrefix(columnPrefix)
        .foreignColumn(foreignColumn) // 多数据源(略)
        .lazy(lazy)
        .build();
  }

1.4.嵌套ResultMap

processNestedResultMappings 该方法的主要作用有

  • 判断该标签是否是嵌套的ResultMap
  • 递归调用resultMapElement方法,解析嵌套ResultMap结构并生成ResultMap的id
  • 将生成的嵌套的ResultMap的id返回给父ResultMap,作为一个ResultMap的ResultMapping的一个属性.
// XMLMapperBuilder
private String processNestedResultMappings(XNode context, 
                                           List<ResultMapping> resultMappings, 
                                           Class<?> enclosingType) {
    // 如果是association,collection,case标签,且不包含select属性
    if (Arrays.asList("association", "collection", "case").contains(context.getName())
        	&& context.getStringAttribute("select") == null) {
        validateCollection(context, enclosingType);
        // 递归调用1.1
        ResultMap resultMap = resultMapElement(
            context, resultMappings, enclosingType);
        return resultMap.getId();
    }
    return null;
}

        从上面代码上能看得出成为嵌套ResultMap的规则为:association,collection,case标签,且不包含select属性.


2.举例

        上面源码已经分析完毕,下面举例来分享下ResultMap的数据结构.

2.1.简单映射

CREATE TABLE `role` (
    `id` int(11) NOT NULL AUTO_INCREMENT,
    `name` varchar(100) DEFAULT NULL COMMENT '角色名称',
    `sort` int(11) DEFAULT NULL COMMENT '排序',
    PRIMARY KEY (`id`)
);
public class Role {
    private Integer id;
    private String name;
    private Integer sort;
    // getter,setter省略
    public Role(Integer id) {
        this.id = id;
    }
}
<mapper namespace="test.vlog.mapper.RoleMapper">
    <resultMap id="roleEntity" type="test.vlog.domian.Role">
        <constructor>
            <idArg column="id" javaType="int"></idArg>
        </constructor>
        <result column="name" property="name"/>
        <result column="sort" property="sort"/>
    </resultMap>
    <select id="getRoleList" resultMap="roleEntity">
        select id,name,sort from role;
    </select>
</mapper>

        roleEntity的ResultMap结构经过第一节的4个方法数据解析过程见下图:
在这里插入图片描述
        经过上述解析过程,最终形成ResultMapping集合结果如下图:
ResultMapping结构
        ResultMap的id生成规则如下图:
ResultMap的id生成
        根据ResultMapping结合生成ResultMap结构规则:

  • (boolean)nestedQueryId(嵌套查询)生成规则:循环遍历每一个ResultMapping,只要有一个ResultMapping的nestedQueryId不为null的,就认为有嵌套查询.
  • (boolean)hasNestedResultMaps(嵌套RM)生成规则:循环遍历每一个ResultMapping,只要有一个ResultMapping有nestedResultMapId,就认为有嵌套的ResultMap.
  • (Set<String>)mappedColumns(列参数)生成规则:循环遍历每一个ResultMapping,列名(column)大写存入.
  • (Set<String>)mappedProperties(属性参数)生成规则:循环遍历每一个ResultMapping,属性名(property)写入.
  • (List<ResultMapping>)constructorResultMappings(构造函数参数)生成规则:如果写了构造函数的idArg的name,但是编译的时候,方法的参数名没有编译进来,就会导致方法的参数名为args0、args1等.会导致与constructor标签中name不一致,从而报错.所以建议不要写constructor的name属性.
  • (List<ResultMapping>)propertyResultMapping生成规则:非constructorResultMappings写入.
  • (List<ResultMapping>)idResultMappings生成规则:id标签的ResultMapping写入.
            最终形成ResultMap结构如下图:
    ResutlMap结构
            最终在Configuration的resultMaps中结构如下图:
    ResultMap在Configuration中的结构

2.2.嵌套映射

        嵌套映射多指一对多或多对多的映射.下面是角色到用户的一个表结构.角色与用户的对应关系为一对多.

CREATE TABLE `user` (
    `id` int(11) NOT NULL,
    `role_id` int(11) DEFAULT NULL COMMENT '角色id',
    `name` varchar(40) DEFAULT NULL COMMENT '用户名称',
    `alisa` varchar(40) DEFAULT NULL COMMENT '别名',
    PRIMARY KEY (`id`)
);
// 角色表见2.1.
public class User {
    private Integer id;
    private Integer roleId;
    private String name;
    private String alisa;
    // getter、setter省略
}

public class Role {
    private Integer id;
    private String name;
    private Integer sort;
    private List<User> users;
    // getter、setter省略
}
<mapper namespace="test.vlog.mapper.RoleMapper">
    <resultMap id="roleEntity" type="test.vlog.domian.Role">
        <id property="id" column="r_id"></id>
        <result property="name" column="r_name"/>
        <result property="sort" column="sort"/>
        <collection property="users" ofType="test.vlog.domian.User">
            <id property="id" column="u_id"></id>
            <result property="name" column="u_name"/>
            <result property="roleId" column="r_id"/>
            <result property="alisa" column="alisa"/>
        </collection>
    </resultMap>

    <select id="getNestRoleList" resultMap="roleEntity">
        select  r.id as r_id,
                r.name as r_name,
                r.sort as sort,
                u.id as u_id,
                u.name as u_name,
                u.alisa as alisa
        from role r
        	inner join user u on r.id = u.role_id;
    </select>
</mapper>

        先看下嵌套ResultMap解析的过程吧:如下图
嵌套ResultMap解析
        好的,那我们看下嵌套的ResultMap最终生成的结构吧:
嵌套ResultMap在Configuration中的结构

2.3.鉴别器

        比如我们有个需求:我们用户表里有name字段和alisa字段,而且有个标志位.当标志位为1时使用name作为用户名,当标志位为2时使用alisa作为用户名.那么通过映射怎么处理呢?

CREATE TABLE `user` (
    `id` int(11) NOT NULL AUTO_INCREMENT,
    `role_id` int(11) DEFAULT NULL COMMENT '角色id',
    `name` varchar(40) DEFAULT NULL COMMENT '用户名称',
    `alisa` varchar(40) DEFAULT NULL COMMENT '别名',
    `tag` tinyint(4) DEFAULT NULL COMMENT '1、使用name,2、使用alisa',
    PRIMARY KEY (`id`)
);
public class User {
    private Integer id;
    private String username;
    // getter、setter省略
}
<resultMap id="userEntity" type="test.vlog.domian.User">
    <id property="id" column="id"></id>
    <discriminator javaType="int" column="tag" >
        <case value="1">
            <result property="username" column="name" />
        </case>
        <case value="2">
            <result property="username" column="alisa"/>
        </case>
    </discriminator>
</resultMap>

<select id="getUserList" resultMap="userEntity">
    select id,name,alisa,tag from user
</select>

        我们先看看鉴别器的处理过程:
鉴别器处理过程
        最终形成的ResultMap结果,见下图:
鉴别器在Configuration中的结构
        好的,三类ResultMap结构的解析过程及之间的数据结构已经分析完毕.那么我们查出来的结果是如何根据这些ResultMap结构进行mysql的列到java类的数据转换呢?这一节我们下期configuration容器(4)–sql映射器–mappedstatement继续分享.喜欢的朋友关注我,且听下回分解.

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值