mybatis resultMap 加载过程详解

目录

mybatis 有9大标签,分别是:
- cache-ref
- cache
- parameterMap
- resultMap
- sql
- select
- insert
- update
- delete

其中 parameterMap 已被官网标记为废弃

cache-refcache 在这个电商横行,分布式肆虐的年代,也不使用 mybatis 缓存了

那么常用的只有其中6种
- resultMap
- sql
- select
- insert
- update
- delete

本节我们针对resultMap进行详细分析

分析结束之后我们可以解答内心的一些疑问:
1. resultMap 有哪些子标签,都用来干什么
2. collection 标签为什么有时候要自己初始化(new ArrayList<>())

流程分析

先从 XMLMapperBuilder 的 configurationElement 说起, 这里存放着9大标签的解析

private void configurationElement(XNode context) {
    try {
      String namespace = context.getStringAttribute("namespace");
      if (namespace == null || namespace.equals("")) {
        throw new BuilderException("Mapper's namespace cannot be empty");
      }
      builderAssistant.setCurrentNamespace(namespace);
      // cache-ref 标签解析
      cacheRefElement(context.evalNode("cache-ref"));
      // cache 标签解析
      cacheElement(context.evalNode("cache"));
      // parameterMap 标签解析
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
      // resultMap 标签解析
      resultMapElements(context.evalNodes("/mapper/resultMap"));
      // sql 标签解析
      sqlElement(context.evalNodes("/mapper/sql"));
      // select|insert|update|delete 标签解析
      buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
    }
  }

遍历resultMap根节点

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

  private ResultMap resultMapElement(XNode resultMapNode) throws Exception {
    return resultMapElement(resultMapNode, Collections.<ResultMapping> emptyList());
  }

首先进入第一道开胃菜:
1. resultMapElements, 它遍历了mapper下面所有的resultMap标签,然后每个都进行 resultMapElement 处理
2. resultMapElement 传入一个空的集合对象,调用重载

resultMapElement 为什么要传入一个空的集合对象呢?

那是因为resultMap标签可以嵌套,但是根节点就是空的

重载的resultMapElement

private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception {
    // 保存当前上下文,用于异常信息回溯
    ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
    // 获得 id 标签
    String id = resultMapNode.getStringAttribute("id",
        resultMapNode.getValueBasedIdentifier());
    // 依次获取默认值 type=>ofType=>resultType>javaType 
    String type = resultMapNode.getStringAttribute("type",
        resultMapNode.getStringAttribute("ofType",
            resultMapNode.getStringAttribute("resultType",
                resultMapNode.getStringAttribute("javaType"))));
    // 获得 继承 resultMap 的 id 
    String extend = resultMapNode.getStringAttribute("extends");
    // 获得 是否自动映射
    Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
    Class<?> typeClass = resolveClass(type);
    Discriminator discriminator = null;
    List<ResultMapping> resultMappings = new ArrayList<ResultMapping>();
    resultMappings.addAll(additionalResultMappings);
    List<XNode> resultChildren = resultMapNode.getChildren();
    // 遍历子节点,依次解析为 ResultMapping 对象,并添加到集合 resultMappings
    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<ResultFlag>();
        if ("id".equals(resultChild.getName())) {
          flags.add(ResultFlag.ID);
        }
        resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
      }
    }
    ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
    try {
      // 解析验证,最终添加到 configuration
      return resultMapResolver.resolve();
    } catch (IncompleteElementException  e) {
      configuration.addIncompleteResultMap(resultMapResolver);
      throw e;
    }
  }

看似很多,实际上就是两步
1. 遍历解析子标签为 ResultMapping 对象
2. 解析根标签 为一个ResultMap并添加到configuration

子标签解析

这里又分三种情况
1. constructor 子标签解析
2. discriminator 子标签解析
3. 其他标签解析

那resultMap 的子标签又有哪些,其他标签都包含哪些呢?

名称 类型
constructor constructor
discriminator discriminator
id 其他
result 其他
association 其他->嵌套
collection 其他->嵌套

其他流程-普通标签 id|result 标签

Xml 例子

<resultMap id="BlogMap" type="com.aya.mapper.Blog">
    <id column="id" property="id"/>
</resultMap>

(id|result|association|collection)标签解析流程

    List<ResultFlag> flags = new ArrayList<ResultFlag>();
    // id 标签加入 flags, 其他的就不加入这个标签
    if ("id".equals(resultChild.getName())) {
      flags.add(ResultFlag.ID);
    }
    resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));

这是最简单最常用的一种解析方式.
1. 添加标识 ID
2. 使用 buildResultMappingFromContext 构建成一个 ResultMapping 添加到集合 resultMappings

buildResultMappingFromContext

private ResultMapping buildResultMappingFromContext(XNode context, Class<?> resultType, List<ResultFlag> flags) throws Exception {
    String property;
    // constructor 标签用name当做属性
    if (flags.contains(ResultFlag.CONSTRUCTOR)) {
      property = context.getStringAttribute("name");
    } else {
    // 普通标签 标签用property当做属性
      property = context.getStringAttribute("property");
    }
    // 废话开始
    String column = context.getStringAttribute("column");
    String javaType = context.getStringAttribute("javaType");
    String jdbcType = context.getStringAttribute("jdbcType");
    String nestedSelect = context.getStringAttribute("select");
    // processNestedResultMappings 嵌套解析在 association|collection 讲解
    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")
    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);
  }

对于简单的id|result标签解析而言,这里面大都是将xml解析成具体的数据

这里要明白两点
1. 每一个 id|result|association|collection 标签,都对应着一个 ResultMapping 对象
2. ResultMapping 是通过builderAssistant.buildResultMapping 构建的

buildResultMapping

 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) {
      // 获得  <id column="id" property="id"/> 节点的 typeHandler 的值,本例未定义,结果为 null
    Class<?> javaTypeClass = resolveResultJavaType(resultType, property, javaType);
    //根据typeHandlerClass获得对象,结果为 null
    TypeHandler<?> typeHandlerInstance = resolveTypeHandler(javaTypeClass, typeHandler);
    // 嵌套查询时解析为多个字段,例如: <association property="title" column="title={title},content={content}" ....
    List<ResultMapping> composites = parseCompositeColumnName(column);
    return new ResultMapping.Builder(configuration, property, column, javaTypeClass)
        .jdbcType(jdbcType)
        .nestedQueryId(applyCurrentNamespace(nestedSelect, true))
        .nestedResultMapId(applyCurrentNamespace(nestedResultMap, true))
        .resultSet(resultSet)
        .typeHandler(typeHandlerInstance)
        .flags(flags == null ? new ArrayList<ResultFlag>() : flags)
        .composites(composites)
        .notNullColumns(parseMultipleColumnNames(notNullColumn))
        .columnPrefix(columnPrefix)
        .foreignColumn(foreignColumn)
        .lazy(lazy)
        .build();
  }

这里终于找到终点了

ResultMapping.Builder 是一个很标准的建造者模式。 符合建造者模式的核心思想。
1. 必要的参数使用构造方法传入
2. 可选的设置使用 withXXX 设置
3. build 之后对象不可变

在build 的时候,做了一个默认的类型解析处理

    public ResultMapping build() {
      // lock down collections
      // 内置标识 不可更改
      resultMapping.flags = Collections.unmodifiableList(resultMapping.flags);
      // 组合列参数不可更改 <association column="title={title},content={content}" ....
      resultMapping.composites = Collections.unmodifiableList(resultMapping.composites);
      // 设置默认的类型处理器
      resolveTypeHandler();
      // 验证嵌套的信息有效
      validate();
      // 返回构建成功的 resultMapping
      // resultMapping 是成员变量
      return resultMapping;
    }


    private void resolveTypeHandler() {
      // 用户没有在标签定义 typeHandler时,使用mybatis默认的typeHandler
      if (resultMapping.typeHandler == null && resultMapping.javaType != null) {
        Configuration configuration = resultMapping.configuration;
        TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
        resultMapping.typeHandler = typeHandlerRegistry.getTypeHandler(resultMapping.javaType, resultMapping.jdbcType);
      }
    }

构造流程 constructor 标签

一个参数只要加入 javaType 就可以匹配到具体的构造函数了,不加javaType时,默认匹配 ctor(Object) 的构造

    <resultMap id="BlogMap" type="com.aya.mapper.Blog"  >
        <constructor>
            <idArg column="pid" javaType="integer"/>
        </constructor>
    </resultMap>

这里走流程 if ("constructor".equals(resultChild.getName())) 的代码块,分析 processConstructorElement 的内部

 private void processConstructorElement(XNode resultChild, Class<?> resultType, List<ResultMapping> resultMappings) throws Exception {
    List<XNode> argChildren = resultChild.getChildren();
    for (XNode argChild : argChildren) {
      List<ResultFlag> flags = new ArrayList<ResultFlag>();
      flags.add(ResultFlag.CONSTRUCTOR);
      if ("idArg".equals(argChild.getName())) {
        flags.add(ResultFlag.ID);
      }
      resultMappings.add(buildResultMappingFromContext(argChild, resultType, flags));
    }
  }

将 constructor 的每个子标签构建成 ResultMapping 对象,添加到 resultMappings

也就是两个步骤
1. 添加标识 ResultFlag.CONSTRUCTOR ,构建 ResultMapping
2. 添加子标签 ,每个 ResultMapping 都有标识ResultFlag.CONSTRUCTOR

其他流程-嵌套标签 - association 标签 | collection 标签

association 用法

association 是一个对象的解析器,有两种用法

    <select id="selectContent" resultType="string">
        SELECT content FROM blog WHERE id = #{id}
    </select>
    <resultMap id="BlogMap" type="com.aya.mapper.Blog" >
        <!-- 直接使用对象,在result为对象赋值 -->
        <association property="content"  javaType="string" >
            <result column="content" />
        </association>
    <!-- 使用 select 引用的查询结果,可以通过column传参 -->
    <!-- 多参 key 是select引用需要用到变量,value是这边查询到的值 {id=id,content=content} -->
        <association property="contentSelect" column="id" select="selectContent"  javaType="string" />
    </resultMap>
public class Blog {

    private String content;

    private String contentSelect;
    //省略 getter , setter
}

collection 用法

collection 是一个集合的解析器,有两种用法

    <select id="selectContentList" resultType="string">
        SELECT content FROM blog WHERE id = #{id}
    </select>
    <resultMap id="BlogMap" type="com.aya.mapper.Blog" >
        <!-- 直接使用集合, 那么bean的集合必须初始化 -->
        <collection property="contentList"  javaType="string" >
            <result column="content"  />
        </collection>
        <!-- 通过select 获取的集合一定不会为null.  所以bean的定义可以不用初始化 -->
        <collection property="contentListSelect" column="id" select="selectContentList"  javaType="list"  />
    </resultMap>
public class Blog {

    public Blog() {
    }
    // 必须初始化
    private List<String> contentList = new ArrayList<>();
    // 可以不用初始化
    private List<String> contentListSelect;
    //省略 getter , setter
}

association | collection 流程分析

这里走的流程是其他标签解析的流程,但是比普通的标签有额外的两个方法要详细说明

在前面讲 buildResultMappingFromContext 的分析的时候,在进行 resultMap 获取是,有一个嵌套的处理。

就是专门用来处理具有嵌套效果的标签的, 而association | collection 刚好就是嵌套标签。

嵌套标签解析

  String nestedResultMap = context.getStringAttribute("resultMap",
        processNestedResultMappings(context, Collections.<ResultMapping> emptyList()));

嵌套多列处理

 List<ResultMapping> composites = parseCompositeColumnName(column);
 ```

### 嵌套标签解析

private String processNestedResultMappings(XNode context, List resultMappings) throws Exception {
if (“association”.equals(context.getName())
|| “collection”.equals(context.getName())
|| “case”.equals(context.getName())) {
if (context.getStringAttribute(“select”) == null) {
ResultMap resultMap = resultMapElement(context, resultMappings);
return resultMap.getId();
}
}
return null;
}


这里做的就是当 `association|collection|case` 标签没有属性`select`的时候,递归解析子标签

### 嵌套多列处理

private List parseCompositeColumnName(String columnName) {
List composites = new ArrayList();
if (columnName != null && (columnName.indexOf(‘=’) > -1 || columnName.indexOf(‘,’) > -1)) {
StringTokenizer parser = new StringTokenizer(columnName, “{}=, “, false);
//例如 column=”{columnKeyA=columnValueA,columnKeyB=columnValueB}” 分两次遍历,添加到composites
while (parser.hasMoreTokens()) {
String property = parser.nextToken();
String column = parser.nextToken();
ResultMapping complexResultMapping = new ResultMapping.Builder(
configuration, property, column, configuration.getTypeHandlerRegistry().getUnknownTypeHandler()).build();
composites.add(complexResultMapping);
}
}
return composites;
}


这里就是来解析标签中 `column="{columnKeyA=columnValueA,columnKeyB=columnValueB}"` 部分的代码

装配成`ResultMapping`集合,在最后build里面的validate进行验证

validate 验证嵌套

// Issue #4 and GH #39: column is optional only in nested resultmaps but not in the rest
if (resultMapping.nestedResultMapId == null && resultMapping.column == null && resultMapping.composites.isEmpty()) {
throw new IllegalStateException(“Mapping is missing column attribute for property ” + resultMapping.property);
}

也就是说,你不可以写成以下形式






## 辨别器流程 discriminator 标签
### 基本使用
<resultMap id="BlogMap" type="com.aya.mapper.Blog" >
    <result column="pid" property="id"/>
    <discriminator javaType="string" column="pid" >
        <case  value="1" resultType="com.aya.mapper.Blog">
            <result column="ptitle" property="title"/>
        </case>
        <case value="2" resultType="com.aya.mapper.Blog">
            <result column="pcontent" property="title"/>
        </case>
    </discriminator>
</resultMap>
<!-- 防止属性自动填充 -->
<select id="selectAll" resultMap="BlogMap"    >
    select id as pid,title as ptitle,content as pcontent from blog
</select>
实体类
```java
public class Blog {

    private String id;
    private String title;
    //省略 getter , setter
}




<div class="se-preview-section-delimiter"></div>

查询结果

[Blog{id='1', title='标题'}, Blog{id='2', title='content2'}, Blog{id='3', title='null'}]




<div class="se-preview-section-delimiter"></div>

理论介绍

discriminator 是一个辨别器. 用switch...case的方式决定让整个resultMap返回什么类型

也就是说,加载的时候只能是某个动态对象,执行查询操作后才知道实际的返回类型.

因为是switch...case,所以不能加入条件判断

  discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);




<div class="se-preview-section-delimiter"></div>

discriminator 的判断的地方可以发现, 如果有discriminator,那么有且仅有一个.

接下来就分析Discriminator对象的创建过程

源码分析

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");
    Class<?> javaTypeClass = resolveClass(javaType);
    @SuppressWarnings("unchecked")
    Class<? extends TypeHandler<?>> typeHandlerClass = (Class<? extends TypeHandler<?>>) resolveClass(typeHandler);
    JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
    Map<String, String> discriminatorMap = new HashMap<String, String>();
    //遍历case节点
    for (XNode caseChild : context.getChildren()) {
     // case 的value
      String value = caseChild.getStringAttribute("value");
      //processNestedResultMappings 解析嵌套的ResultMapping,并返回创建的名称
      String resultMap = caseChild.getStringAttribute("resultMap", processNestedResultMappings(caseChild, resultMappings));
      discriminatorMap.put(value, resultMap);
    }
    // 构建 Discriminator
    return builderAssistant.buildDiscriminator(resultType, column, javaTypeClass, jdbcTypeEnum, typeHandlerClass, discriminatorMap);
  }




<div class="se-preview-section-delimiter"></div>
  1. 每个value都对应着一个ResultMapping名称,在根据查询结果进行转换
  2. 使用构建助手构建辨别器
public Discriminator buildDiscriminator(
      Class<?> resultType,
      String column,
      Class<?> javaType,
      JdbcType jdbcType,
      Class<? extends TypeHandler<?>> typeHandler,
      Map<String, String> discriminatorMap) {
      // 将辨别器节点构建成一个 ResultMapping
    ResultMapping resultMapping = buildResultMapping(
        resultType,
        null,
        column,
        javaType,
        jdbcType,
        null,
        null,
        null,
        null,
        typeHandler,
        new ArrayList<ResultFlag>(),
        null,
        null,
        false);
    Map<String, String> namespaceDiscriminatorMap = new HashMap<String, String>();
    //构建辨别器的所有子节点
    for (Map.Entry<String, String> e : discriminatorMap.entrySet()) {
      String resultMap = e.getValue();
      // 使用将简写改为全称
      resultMap = applyCurrentNamespace(resultMap, true);
      namespaceDiscriminatorMap.put(e.getKey(), resultMap);
    }
    // 用建造者模式创建构建器
    return new Discriminator.Builder(configuration, resultMapping, namespaceDiscriminatorMap).build();




<div class="se-preview-section-delimiter"></div>

这里就通过 Discriminator 的建造者创建了对象.

Discriminator 是一个根据结果动态选择返回类型的一个标签,单纯的分析解析过程,意义不大

根标签解析

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




<div class="se-preview-section-delimiter"></div>

通过上一层的 builderAssistant.addResultMap ,实际上添加到哪里去了呢?

 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 = configuration.getResultMap(extend);
      List<ResultMapping> extendedResultMappings = new ArrayList<ResultMapping>(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) {
        Iterator<ResultMapping> extendedResultMappingsIter = extendedResultMappings.iterator();
        while (extendedResultMappingsIter.hasNext()) {
          if (extendedResultMappingsIter.next().getFlags().contains(ResultFlag.CONSTRUCTOR)) {
            extendedResultMappingsIter.remove();
          }
        }
      }
      resultMappings.addAll(extendedResultMappings);
    }
    //ResultMap.Builder 构建 ResultMap.  根标签 resultMap 
    ResultMap resultMap = new ResultMap.Builder(configuration, id, type, resultMappings, autoMapping)
        .discriminator(discriminator)
        .build();
    // 将根标签解析的对象resultMap添加到configuration
    configuration.addResultMap(resultMap);
    return resultMap;
  }




<div class="se-preview-section-delimiter"></div>

实际上那么多的解析从宏观上就只有两步
1. 将resultMap的所有子标签解析成ResultMapping对象
2. 将resultMap标签构建成ResultMap对象添加到configuration

详解

构造器多参详解

    <resultMap id="BlogMap" type="com.aya.mapper.Blog"  >
        <constructor>
            <idArg column="pid" name="id" javaType="integer"/>
            <arg column="ptitle" name="title" javaType="string"/>
        </constructor>
    </resultMap>




<div class="se-preview-section-delimiter"></div>
public class Blog implements Serializable{
    private Integer id;
    private String title;
    public Blog(Integer id, String title) {
        this.id = id;
        this.title = title;
    }
}

以上面的代码为例,查询时在创建ResultMap时,抛出异常

Caused by: org.apache.ibatis.builder.BuilderException: Error in result map 'com.aya.mapper.BlogMapper.BlogMap'. Failed to find a constructor in 'com.aya.mapper.Blog' by arg names [id, title]. There might be more info in debug log.
    at org.apache.ibatis.mapping.ResultMap$Builder.build(ResultMap.java:132)
    at org.apache.ibatis.builder.MapperBuilderAssistant.addResultMap(MapperBuilderAssistant.java:213)
    at org.apache.ibatis.builder.ResultMapResolver.resolve(ResultMapResolver.java:47)
    at org.apache.ibatis.builder.xml.XMLMapperBuilder.resultMapElement(XMLMapperBuilder.java:285)
    at org.apache.ibatis.builder.xml.XMLMapperBuilder.resultMapElement(XMLMapperBuilder.java:252)
    at org.apache.ibatis.builder.xml.XMLMapperBuilder.resultMapElements(XMLMapperBuilder.java:244)
    at org.apache.ibatis.builder.xml.XMLMapperBuilder.configurationElement(XMLMapperBuilder.java:116)
    ... 29 more

打破脑袋也想不明白,明明已经提供了 id,title 类型的构造,参数类型也能匹配。为什么找不到呢?

跟进错误堆栈 ResultMap$Builder.build(ResultMap.java:132)

final List<String> actualArgNames = argNamesOfMatchingConstructor(constructorArgNames);
        if (actualArgNames == null) {
          throw new BuilderException("Error in result map '" + resultMap.id
              + "'. Failed to find a constructor in '"
              + resultMap.getType().getName() + "' by arg names " + constructorArgNames
              + ". There might be more info in debug log.");

argNamesOfMatchingConstructor 的结果为 null,抛出异常.

private List<String> argNamesOfMatchingConstructor(List<String> constructorArgNames) {
      // 获得构造器
      Constructor<?>[] constructors = resultMap.type.getDeclaredConstructors();
      for (Constructor<?> constructor : constructors) {
        Class<?>[] paramTypes = constructor.getParameterTypes();
        // 判断长度相等
        if (constructorArgNames.size() == paramTypes.length) {
        // 获得 constructor 参数名字列表为 : arg0, arg1
          List<String> paramNames = getArgNames(constructor);
          // [id,title].contains(arg0,arg1) 失败,结束循环
          if (constructorArgNames.containsAll(paramNames)
              && argTypesMatch(constructorArgNames, paramTypes, paramNames)) {
            return paramNames;
          }
        }
      }
      return null;
    }

这里的主要问题就在于 getArgNames 获得的居然是arg0,arg1, 并不是我们写好的参数id,title

private List<String> getArgNames(Constructor<?> constructor) {
      List<String> paramNames = new ArrayList<String>();
      List<String> actualParamNames = null;
      final Annotation[][] paramAnnotations = constructor.getParameterAnnotations();
      int paramCount = paramAnnotations.length;
      for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
        String name = null;
        for (Annotation annotation : paramAnnotations[paramIndex]) {
        // 优先判断是否有注解
          if (annotation instanceof Param) {
            name = ((Param) annotation).value();
            break;
          }
        }
        if (name == null && resultMap.configuration.isUseActualParamName() && Jdk.parameterExists) {
          if (actualParamNames == null) {
          // 遍历构造器的参数列表,获取参数名称
            actualParamNames = ParamNameUtil.getParamNames(constructor);
          }
          if (actualParamNames.size() > paramIndex) {
            name = actualParamNames.get(paramIndex);
          }
        }
        paramNames.add(name != null ? name : "arg" + paramIndex);
      }
      return paramNames;
    }
  }

现在就要解决通过反射无法获得参数名称的问题.

这里 getArgNames 提供给了我们一个额外的选择,在参数前面加入 @Param 注解.

既然是额外的选择,那么也就是说JDK也提供了一个参数,编译时,把参数名称也保存起来的方式

Param注解 是mybatis 提供的一个扩展,而方案2,方案3都是开启JDK8的特性编译器保留方法参数名字

方案1 Param注解

推荐使用

public class Blog implements Serializable{
    private Integer id;
    private String title;
    public Blog(@Param("id")Integer id,@Param("title") String title) {
        this.id = id;
        this.title = title;
    }
}

使用这个虽然写起来麻烦了一些,但是利于维护,不用担心别人看不懂。或者由于不同的环境导致不一样的效果

方案2 maven 设置

添加 maven 插件

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.1</version>
    <configuration>
        <compilerArgument>-parameters</compilerArgument>
        <encoding>UTF-8</encoding>
        <source>1.8</source>
        <target>1.8</target>
    </configuration>
</plugin>

引用: https://blog.csdn.net/aitangyong/article/details/54376991

方案3 idea设置

打开File => Settings

依次选择 Buld,Execution,Deployment => Compiler => Java Compiler

设置 Additional command line parameters: -parameters

方案4 黑科技

切勿使用

黑科技: 既然不不开启特性,又想对应,那我就满足你的原理

    <resultMap id="BlogMap" type="com.aya.mapper.Blog"  >
        <constructor>
            <idArg column="pid" name="arg0" javaType="integer"/>
            <arg column="ptitle" name="arg1" javaType="string"/>
        </constructor>
    </resultMap>

总结

  1. resultMap 有6个子标签
  2. select属性关联集合不会为null

猜你喜欢

转载自blog.csdn.net/mz4138/article/details/81132980
今日推荐