MyBatis联合主键结果集与SQL查询结果不一致的问题

一、问题

如果select中的联合主键组合成的key不唯一(当只select部分联合主键时可能发生),那么就会把相同的key合并成一条数据。

例如KEY_AKEY_BKEY_C是联合主键

KEY_AKEY_BKEY_Cdes
A1B1C1数据1
A1B2C1数据2
A1B2C2数据3

如果只返回KEY_AKEY_B,那么SQL查询结果是三条,但是MyBatis返回方结果只有2条,数据3的key和数据2的key是一样的,所以不会返回数据3,MyBatis返回的结果为

KEY_AKEY_BKEY_Cdes
A1B1C1数据1
A1B2C1数据2

如果是单主键则没有问题

二、问题分析

1.联合主键时MyBatis的处理方式

MyBatis调用query(Statement statement, ResultHandler resultHandler)方法查询

package org.apache.ibatis.executor.statement;
...
public class PreparedStatementHandler extends BaseStatementHandler {
  ...
  @Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    //处理SQL的查询结果
    return resultSetHandler.<E> handleResultSets(ps);
  }
  ...
}

类所在JAR包如下图: 输入图片说明

其中resultSetHandler.<E> handleResultSets(ps);方法的实现如下

package org.apache.ibatis.executor.resultset;
...
public class DefaultResultSetHandler implements ResultSetHandler {
  ...
  @Override
  public List<Object> handleResultSets(Statement stmt) throws SQLException {
    ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
    final List<Object> multipleResults = new ArrayList<Object>();
    int resultSetCount = 0;
    ResultSetWrapper rsw = getFirstResultSet(stmt);
    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
    int resultMapCount = resultMaps.size();
    validateResultMapsCount(rsw, resultMapCount);
    while (rsw != null && resultMapCount > resultSetCount) {
      ResultMap resultMap = resultMaps.get(resultSetCount);
      //处理结果集
      handleResultSet(rsw, resultMap, multipleResults, null);
      rsw = getNextResultSet(stmt);
      cleanUpAfterHandlingResultSet();
      resultSetCount++;
    }
    String[] resultSets = mappedStatement.getResulSets();
    if (resultSets != null) {
      while (rsw != null && resultSetCount < resultSets.length) {
        ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
        if (parentMapping != null) {
          String nestedResultMapId = parentMapping.getNestedResultMapId();
          ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
          handleResultSet(rsw, resultMap, null, parentMapping);
        }
        rsw = getNextResultSet(stmt);
        cleanUpAfterHandlingResultSet();
        resultSetCount++;
      }
    }
    return collapseSingleResultList(multipleResults);
  }
  ...
}

其中调用handleResultSet(rsw, resultMap, multipleResults, null);处理结果集

package org.apache.ibatis.executor.resultset;
...
public class DefaultResultSetHandler implements ResultSetHandler {
    ...
    private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
    try {
      if (parentMapping != null) {
        handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
      } else {
        if (resultHandler == null) {
          DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
          //处理结果集
          handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
          multipleResults.add(defaultResultHandler.getResultList());
        } else {
          handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
        }
      }
    } finally {
      // issue #228 (close resultsets)
      closeResultSet(rsw.getResultSet());
    }
  }
  ...
}  

其中handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);处理结果集。

package org.apache.ibatis.executor.resultset;
...
public class DefaultResultSetHandler implements ResultSetHandler {
    ...
	private 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(rsw, resultMap, resultHandler, rowBounds, parentMapping);处理结果集

package org.apache.ibatis.executor.resultset;
...
public class DefaultResultSetHandler implements ResultSetHandler {
    ...
	private void handleRowValuesForNestedResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
    final DefaultResultContext<Object> resultContext = new DefaultResultContext<Object>();
    skipRows(rsw.getResultSet(), rowBounds);
    Object rowValue = null;
    //循环每条记录
    while (shouldProcessMoreRows(resultContext, rowBounds) && rsw.getResultSet().next()) {
      final ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, null);
      //生成rowKey
      final CacheKey rowKey = createRowKey(discriminatedResultMap, rsw, null);
      //通过rowKey去nestedResultObjects取partialObject
      Object partialObject = nestedResultObjects.get(rowKey);
      // issue #577 && #542
  	  //【#resultOrdered为true时】resultOrdered官方解释:这个设置仅针对嵌套结果 select 语句适用:如果为 true,就是假设包含了嵌套结果集或是分组了,这样的话当返回一个主结果行的时候,就不会发生有对前面结果集的引用的情况。这就使得在获取嵌套的结果集的时候不至于导致内存不够用。默认值:false。
      if (mappedStatement.isResultOrdered()) {
        if (partialObject == null && rowValue != null) {
          //会clear,这样下个循环中partialObject就为null了
          nestedResultObjects.clear();
          //加的是上一行的数据rowValue
          storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet());
        }
        //保存当前行的数据,给下一行用
        rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, rowKey, null, partialObject);
      } else {
        rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, rowKey, null, partialObject);
        //如果partialObject为null,即在nestedResultObjects中通过rowKey查询不到结果;正常是null的,除非生成的rowKey与其它行的数据的rowKey重复了,这样就不会调用storeObject方法,不会把数据加到resultHandler中
        if (partialObject == null) {
          //在resultHandler的list中增加当前行数据
          storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet());
        }
      }
    }
    //【#resultOrdered为true时】isResultOrdered()为true,且是最后一行,增加当前行数据到resultHandler
    if (rowValue != null && mappedStatement.isResultOrdered() && shouldProcessMoreRows(resultContext, rowBounds)) {
      storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet());
    }
  }
  ...
}

其中调用createRowKey(discriminatedResultMap, rsw, null);生成rowKey

package org.apache.ibatis.executor.resultset;
...
public class DefaultResultSetHandler implements ResultSetHandler {
   ...  
   private CacheKey createRowKey(ResultMap resultMap, ResultSetWrapper rsw, String columnPrefix) throws SQLException {
    final CacheKey cacheKey = new CacheKey();
    cacheKey.update(resultMap.getId());
    //主键字段组成的list
    List<ResultMapping> resultMappings = getResultMappingsForRowKey(resultMap);
    if (resultMappings.size() == 0) {
      if (Map.class.isAssignableFrom(resultMap.getType())) {
        createRowKeyForMap(rsw, cacheKey);
      } else {
        createRowKeyForUnmappedProperties(resultMap, rsw, cacheKey, columnPrefix);
      }
    } else {//如果表设置了主键
      //修改cacheKey属性
      createRowKeyForMappedProperties(resultMap, rsw, cacheKey, resultMappings, columnPrefix);
    }
    return cacheKey;
  }
  ...
}

其中createRowKeyForMappedProperties(resultMap, rsw, cacheKey, resultMappings, columnPrefix);修改cacheKey属性

package org.apache.ibatis.executor.resultset;
...
public class DefaultResultSetHandler implements ResultSetHandler {
   ...  
   private void createRowKeyForMappedProperties(ResultMap resultMap, ResultSetWrapper rsw, CacheKey cacheKey, List<ResultMapping> resultMappings, String columnPrefix) throws SQLException {
    //循环主键包含的字段
    for (ResultMapping resultMapping : resultMappings) {
      if (resultMapping.getNestedResultMapId() != null && resultMapping.getResultSet() == null) {
        // Issue #392
        final ResultMap nestedResultMap = configuration.getResultMap(resultMapping.getNestedResultMapId());
        createRowKeyForMappedProperties(nestedResultMap, rsw, cacheKey, nestedResultMap.getConstructorResultMappings(),
            prependPrefix(resultMapping.getColumnPrefix(), columnPrefix));
      } else if (resultMapping.getNestedQueryId() == null) {
        //主键字段之一
        final String column = prependPrefix(resultMapping.getColumn(), columnPrefix);
        final TypeHandler<?> th = resultMapping.getTypeHandler();
        //查询出来的结果集中的字段的名称组成的list
        List<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
        // Issue #114
        //查询出来的结果集中是否包含当前主键字段之一
        if (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH))) {
          final Object value = th.getResult(rsw.getResultSet(), column);
          if (value != null) {
            //如果是,且字段中有值,把column和value更新到cacheKey中
            cacheKey.update(column);
            cacheKey.update(value);
          }
        }
      }
    }
  }
  ...
}

所以,如果查询出的主键字段组合后不唯一,那么生成的cacheKey就不唯一,那么在handleRowValuesForNestedResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)Object partialObject = nestedResultObjects.get(rowKey);的partialObject就不为空。

2.单主键时MyBatis的处理方式

在上述handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)方法中会调用handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);方法

package org.apache.ibatis.executor.resultset;
...
public class DefaultResultSetHandler implements ResultSetHandler { 
  ...
  private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
      throws SQLException {
    DefaultResultContext<Object> resultContext = new DefaultResultContext<Object>();
    skipRows(rsw.getResultSet(), rowBounds);
    //循环增加行
    while (shouldProcessMoreRows(resultContext, rowBounds) && rsw.getResultSet().next()) {
      ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, null);
      Object rowValue = getRowValue(rsw, discriminatedResultMap);
      storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet());
    }
  }
  ...
}

三、解决方式

1.SQL中把select中的主键字段写全

<resultMap id="BaseResultMap" type="com.test.model.Entity">
    <id column="KEY_A" jdbcType="DECIMAL" property="keyA" />
    <id column="KEY_B" jdbcType="DECIMAL" property="keyB" />
    <id column="KEY_C" jdbcType="DECIMAL" property="keyC" />
    <result column="DES" jdbcType="DECIMAL" property="des" />
</resultMap>
<select id="findEntity" parameterType="com.test.model.Entity" resultMap="ResultMap">
      select KEY_A,KEY_B,KEY_C,DES
      from ENTITY
</select>

2.设置resultOrderedtrue

<resultMap id="BaseResultMap" type="com.test.model.Entity">
    <id column="KEY_A" jdbcType="DECIMAL" property="keyA" />
    <id column="KEY_B" jdbcType="DECIMAL" property="keyB" />
    <id column="KEY_C" jdbcType="DECIMAL" property="keyC" />
    <result column="DES" jdbcType="DECIMAL" property="des" />
</resultMap>
<select id="findEntity" parameterType="com.test.model.Entity" resultMap="ResultMap"
        resultOrdered="true">
      select KEY_A,KEY_B,DES
      from ENTITY
</select>

转载于:https://my.oschina.net/jerrypan/blog/1522772

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值