Mybatis返回结果字段与Java PO类字段映射

1、现象

mybatis版本从3.4.2升级到3.5.6之后,碰到一个问题select 值为空的问题,在3.4.2的版本下可以正常筛选出来。对应的mapper.xml文件如下所示。 结果digtAccArId字段为空,crdtfwdfacepicfilerte不为空。

<?xml vsersion="1.0" encoding="UTF-8"?>
<sqlMap namespace="xxxx">
    <resultMap id = "baseResult" type="com.test.info.testPO">
        <result column ="Digt_Acc_Ar_ID" property = "digtAccArId" />
        <result column ="CrdtFwdFacePicFileRte" property = "crdtfwdfacepicfilerte" />
    </resultMap>
    <sql id = "base_list">
        Digt_Acc_Ar_ID as digtAccArId,
        CrdtFwdFacePicFileRte as crdtfwdfacepicfilerte
    </sql>
</sqlMap>

2、概要分析

首先列举一下mybaits的数据库列和java的PO字段的映射方式:

  • 通过select ... as的这种加别名的方式映射

  • 通过resultMap的方式映射

  • 通过数据库表列明与PO字段名字相似的方式,并开启mapUnderscoreToCamelCase

下面通过举例说明

com.test.info.po的结构如下

public Class TestPO {
    private String digtAccArId;
    private String crdtfwdfacepicfilerte;
}

数据库表结构如下

create table test
(
    Digt_Acc_Ar_ID varchar(23),
    CrdtFwdFacePicFileRte varchar(10)
)

方式一、通过select ... as的这种加别名的方式映射,as后的字段是和PO的属性一致

<sql id = "base_list">
        Digt_Acc_Ar_ID as digtAccArId,
        CrdtFwdFacePicFileRte as crdtfwdfacepicfilerte
 </sql>

方式二、通过resultMap的方式映射 column是数据库字段列名,property是PO的属性名

<resultMap id = "baseResult" type="com.test.info.testPO">
    <result column ="Digt_Acc_Ar_ID" property = "digtAccArId" />
    <result column ="CrdtFwdFacePicFileRte" property = "crdtfwdfacepicfilerte" />
</resultMap>

方式三、保持数据库列字段名和PO的名字类似(解析的时候,大小写不敏感,开启mapUnderscoreToCamelCase,会把“_”去掉)

<sql id = "base_list">
        Digt_Acc_Ar_ID,
        CrdtFwdFacePicFileRte
 </sql>

上述mapper.xml中同时写了两种,理论上来说select出来的数据映射不上是合乎情理的。然而事实在3.4.2的版本又是可以达到select的效果的。此时,我们只能从源码出发,一探究竟。

3、源码分析

3.1 、DefaultResultSetHandler

从现象上来说,问题应该出在数据映射上,目光直接聚焦到DefaultResultSetHandler。 可以看到在handleRowValues这个方法里面会根据是否存在resultMap来做不同映射处理。

/**
HANDLE ROWS FOR SIMPLE RESULTMAP
*/
public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
    if (resultMap.hasNestedResultMaps()) {
      ensureNoRowBounds();
      checkResultHandler();
      handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
    } else {
      handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
    } 
  }

在handleRowValuesForNestedResultMap中会调用getRowValue。 在getRowValue中applyAutomaticMappings和applyPropertyMappings

private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap) throws SQLException {
    ResultLoaderMap lazyLoader = new ResultLoaderMap();
    Object rowValue = createResultObject(rsw, resultMap, lazyLoader, null);
    if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
      MetaObject metaObject = this.configuration.newMetaObject(rowValue);
      boolean foundValues = this.useConstructorMappings;
      if (shouldApplyAutomaticMappings(resultMap, false)) {
          //重点关注主要方法
        foundValues = (applyAutomaticMappings(rsw, resultMap, metaObject, null) || foundValues);
      }
        //重点关注主要方法
      foundValues = (applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, null) || foundValues);
      foundValues = (lazyLoader.size() > 0 || foundValues);
      rowValue = (foundValues || this.configuration.isReturnInstanceForEmptyRow()) ? rowValue : null;
    } 
    return rowValue;
  }

applyAutomaticMappings 中一个重要的步骤就是创建mapping映射对。UnMappedColumnAutoMapping存放在resultSet中但是没有在resultMap中的那些字段。(resultSet 中用select出来的字段和 resultMap中的column)

private List<UnMappedColumnAutoMapping> createAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
    String mapKey = resultMap.getId() + ":" + columnPrefix;
    List<UnMappedColumnAutoMapping> autoMapping = this.autoMappingsCache.get(mapKey);
    if (autoMapping == null) {
      autoMapping = new ArrayList<UnMappedColumnAutoMapping>();
        //获取不在resultMap中那些字段()
      List<String> unmappedColumnNames = rsw.getUnmappedColumnNames(resultMap, columnPrefix);
      for (String columnName : unmappedColumnNames) {
        String propertyName = columnName;
        if (columnPrefix != null && !columnPrefix.isEmpty())
        {
          
          if (columnName.toUpperCase(Locale.ENGLISH).startsWith(columnPrefix)) {
            propertyName = columnName.substring(columnPrefix.length());
          } else {
            continue;
          } 
        }
        String property = metaObject.findProperty(propertyName, this.configuration.isMapUnderscoreToCamelCase());
        if (property != null && metaObject.hasSetter(property)) {
          //这里是3.4.4开始增加的,做了一个resultMap和默认处理   
          if (resultMap.getMappedProperties().contains(property)) {
            continue;
          } 
          Class<?> propertyType = metaObject.getSetterType(property);
          if (this.typeHandlerRegistry.hasTypeHandler(propertyType, rsw.getJdbcType(columnName))) {
            TypeHandler<?> typeHandler = rsw.getTypeHandler(propertyType, columnName);
            autoMapping.add(new UnMappedColumnAutoMapping(columnName, property, typeHandler, propertyType.isPrimitive())); continue;
          } 
          this.configuration.getAutoMappingUnknownColumnBehavior()
            .doAction(this.mappedStatement, columnName, property, propertyType);
          continue;
        } 
        this.configuration.getAutoMappingUnknownColumnBehavior()
          .doAction(this.mappedStatement, columnName, (property != null) ? property : propertyName, null);
      } 
      
      this.autoMappingsCache.put(mapKey, autoMapping);
    } 
    return autoMapping;
  }

默认取映射关系的时候,会根据useCamelCaseMapping把下划线去掉。该配置在mybatis-config.xml中由mapUnderscoreToCamelCase指定(参考:https://mybatis.org/mybatis-3/zh/configuration.html

 public String findProperty(String name, boolean useCamelCaseMapping) {
    if (useCamelCaseMapping) {
      name = name.replace("_", "");
    }
    return findProperty(name);
  }

在3.4.4版本开始增加了一段,正式这一段的加入,导致了3.4.2 与 3.4.4之间的取值差异

if (resultMap.getMappedProperties().contains(property)) {
            continue;
          } //这里是3.4.4开始增加的,做了一个resultMap和默认处理

3.2 、举例说明

针对问题中的sqlmap,在经过查询之后再resultSet的结果中会存在类似一下的结果:“digtAccArId”:“1234567”, “crdtfwdfacepicfilerte”:“1234” (具体的结果不尽相同,粗略表达,大小写不敏感)

在3.4.2的版本中处理之后的映射关系应该是:“digtAccArId”:“1234567”, “crdtfwdfacepicfilerte”:“1234”, “Digt_Acc_Ar_ID” “CrdtFwdFacePicFileRte”

在3.4.4的版本中处理之后的映射关系应该是: “Digt_Acc_Ar_ID” :“ ”“CrdtFwdFacePicFileRte”:“”

所以导致了前述的问题。

4、总结

  • Mybatis版本3.4.2升级到3.4.4 修改了默认映射取值的规则,从之前无论是否存在resultMap映射都会做一个默认处理,改为了仅针对不存在的resultMap的字段做默认处理

  • 默认处理中的会把筛选结果的字段中“_”去掉,大小写不敏感。所以如果数据库中存在类似abc和ab_c这样的两个字段会有问题


版权声明:本文为SFiFei原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。