亲宝软件园·资讯

展开

详解MyBatis ResultSetHandler 结果集的解析过程

念念清晰 人气:0

正文

mybatis版本:3.5.12

mybatis通过Executor查询出结果后,通常返回的是一个List结构,再根据用户调用的API把List结构转为指定结构。

mybatis中封装了一个类叫做ResultSetHandler它用来处理查询数据库得到的结果集,并把结果集解析为用户指定类型的数据。它的调用时机就是在查询玩数据库之后,调用时机如下

public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
  PreparedStatement ps = (PreparedStatement) statement;
  ps.execute();
  return resultSetHandler.handleResultSets(ps);
}

第一步先获取PreparedStatement对象,第二部执行execute方法查询数据库,第三步就是使用ResultSetHandler处理结果集。接下来就来看下resultSetHandler是如何处理结果集对象的。

它的逻辑在ResultSetHandler#handleResultSets方法中

ResultSetHandler#handleResultSets

ResultSetHandler是一个接口,它只有一个实现类DefaultResultSetHandler,下面是handleResultSets方法关的键代码。我把此核心逻辑代码分为了5部分,后面章节详细介绍

public List<Object> handleResultSets(Statement stmt) throws SQLException {
  // 第一部分:用来缓存最后的返回值,每条记录处理完之后都会存入该集合中
  final List<Object> multipleResults = new ArrayList<>();
  int resultSetCount = 0;
  /*
   * ResultSetWrapper对结果集(ResultSet)进行了包装
   * getFirstResultSet获取了Statement中的第一个结果集对象,注:一般情况下只有一个结果集,如果调用存储过程可能就会获得多个结果集
   */
  ResultSetWrapper rsw = getFirstResultSet(stmt);
  // 第二部分
  // 1. 先处理mappedStatement中的ResultMap标签(每个XML的SQL语句都被映射成了MappedStatement对象。)
  // 每个SQL执行的返回结果有可能是多个resultMap标签共同组成的。可能是多结果集
  List<ResultMap> resultMaps = mappedStatement.getResultMaps();
  int resultMapCount = resultMaps.size();
  validateResultMapsCount(rsw, resultMapCount);
  // 第三部分
  while (rsw != null && resultMapCount > resultSetCount) {
    // MappedStatement中的ResultMap数量应该和 结果集的数量一致
    ResultMap resultMap = resultMaps.get(resultSetCount);
    // 处理结果集,这是该方法中最重要的步骤
    handleResultSet(rsw, resultMap, multipleResults, null);
    // 获取下一个结果集(多结果集情况)
    rsw = getNextResultSet(stmt);
    // nestedResultObjects清空该对象,该对象是一个缓存
    cleanUpAfterHandlingResultSet();
    resultSetCount++;
  }
  // 第四部分
  // 2. 先处理mappedStatement中的ResultSets标签. 因为解析ResultMap的时候,可能ResultMap中包含ResultSet标签,而ResultSet标签并未解析
  String[] resultSets = mappedStatement.getResultSets();
  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);
}

该代码主要分为两个大逻辑:

比如有如下存储函数:getuserand_orders

create procedure get_user_and_orders(in id int)
begin
    select * from user;
    select * from order;
END;

该函数的业务意义是:查询所有的用户,和所有的订单。在Mapper中定义的resultMap如下

<resultMap id="userMap" type="user">
    <id property="id" column="id"/>
    <result property="username" column="username"/>
    <result property="birthday" column="birthday"/>
    <result property="password" column="password"/>
    <association property="orderList" resultSet="orders">
        <result property="name" column="name"/>
    </association>
</resultMap>
<!--resultSets的顺序不能随意放置,否则会导致结果集为空-->
<select id="selectMoreResults2" statementType="CALLABLE" resultSets="users,orders" resultMap="userMap">
    {call get_user_and_orders(1)}
</select>

此时如果用户执行了存储函数,那么PS中的结果集 会有两个,分别是users和orders。mybatis在处理结果集时发现。结果集中有两个对象,先处理第一个,第一个结果集为users,自然要映射为User对象,给User对象的orderList属性赋值时发现结果集中没有关于订单的数据,因为订单的数据在第二个结果集中。这时候就会在第二部再去处理第二个结果集。把订单的结果集数据映射到User的orderList属性中。

下面我们详细分析上面这一长串代码。

第一部分:ResultSetWrapper

首先我们来看第一部分的三行代码

final List<Object> multipleResults = new ArrayList<>();
int resultSetCount = 0;
ResultSetWrapper rsw = getFirstResultSet(stmt);
public class ResultSetWrapper {
  private final ResultSet resultSet;
  private final TypeHandlerRegistry typeHandlerRegistry;
  // 结果集中的列名集合
  private final List<String> columnNames = new ArrayList<>();
  // java名称集合
  private final List<String> classNames = new ArrayList<>();
  private final List<JdbcType> jdbcTypes = new ArrayList<>();
  // ResultMap标签中指定的映射(重要!)
  private final Map<String, List<String>> mappedColumnNamesMap = new HashMap<>();
  // ResultMap标签中未指定的映射字段(重要!)
  private final Map<String, List<String>> unMappedColumnNamesMap = new HashMap<>();
}

第二部分:验证rsw对象

上面获取了rsw对象(ResultSetWrapper,后面简称rsw了)后,接下来需要验证rsw对象。第二部分的三行代码如下

List<ResultMap> resultMaps = mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
validateResultMapsCount(rsw, resultMapCount);
<select id="selectMoreResults1" statementType="CALLABLE" resultMap="users,authors">
    {call get_user_and_authors(1)}
</select>

第三部分:遍历rsw中的结果集

接下来就是要遍历rsw中的结果集对象。并把结果集中的每条记录都根据resultMap标签定义的映射关系转化为指定类型的数据。并把它加入到第一部分提到的multipleResults集合中。第三部分的代码如下

while (rsw != null && resultMapCount > resultSetCount) {
  ResultMap resultMap = resultMaps.get(resultSetCount);
  // 处理结果集,这是该方法中最重要的步骤
  handleResultSet(rsw, resultMap, multipleResults, null);
  // 获取下一个结果集(多结果集情况)
  rsw = getNextResultSet(stmt);
  // nestedResultObjects清空该对象,该对象是一个缓存
  cleanUpAfterHandlingResultSet();
  resultSetCount++;
}

改代码的意思是,当rsw存在并且resultMapCount > resultSetCount时

第四部分:处理ResultSets标签

如果在第一部分到第三部分的循环中,顺序处理完结果集对象之后,resultSetCount数量还是大于resultMapCount,那么就证明PS对象返回的是多结果集,并且多结果集值对应了一个映射关系,此时就需要解析这个ResultSets标签。它的解析流程和第三部分一样,重点就在于handleResultSet方法。下面使用一个案例来详细说明为什么会有这部分的解析。

create procedure get_user_and_orders(in id int)
begin
    select * from user;
    select * from order;
END;
<resultMap id="userMap" type="user">
    <id property="id" column="id"/>
    <result property="username" column="username"/>
    <result property="birthday" column="birthday"/>
    <result property="password" column="password"/>
    <association property="orderList" resultSet="orders">
        <result property="name" column="name"/>
    </association>
</resultMap>
<!--resultSets的顺序不能随意放置,否则会导致结果集为空-->
<select id="selectMoreResults2" statementType="CALLABLE" resultSets="users,orders" resultMap="userMap">
    {call get_user_and_orders(1)}
</select>

第五部分:collapseSingleResultList

最后一部分很简单,它只是把最后返回的结果进行判断:如果返回结果multipleResults集合大小为1,则只返回集合中的这个元素,否则返回原对象本身

private List<Object> collapseSingleResultList(List<Object> multipleResults) {
  return multipleResults.size() == 1 ? (List<Object>) multipleResults.get(0) : multipleResults;
}

总结

该篇讲述了mybatis在执行完数据库后进行结果集的大致解析过程。

后续我会带来handleResultSet方法的解析~

更多关于MyBatis ResultSetHandler结果集的资料请关注其它相关文章!

加载全部内容

相关教程
猜你喜欢
用户评论