前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >MyBatis源码阅读(七) --- 查询结果集封装流程

MyBatis源码阅读(七) --- 查询结果集封装流程

作者头像
终有救赎
发布2024-01-30 08:59:45
2220
发布2024-01-30 08:59:45
举报
文章被收录于专栏:多线程多线程
一、概述

前面一篇文章我们分析了mapper方法具体的执行流程,跟踪代码到了resultSetHandler.handleResultSets()处理结果集这里,这也是Mybatis处理数据的最后一个步骤了。

试想一下,如果让我们自己组装结果,我们该如何实现呢,可以大概想一下。

如果是我们自己实现的话,有几个关键的步骤:

  • 1、准备一个List集合存放结果集;
  • 2、肯定要拿到我们在Mapper.xml中配置的resultType属性,拿到之后,通过反射,我们应该可以拿到对应的Class类;
  • 3、拿到Class类之后,那么就可以拿到构造方法new一个空对象,属性都是空的,还没有赋值;
  • 4、获取类有哪些属性,然后根据resultSetHandler和属性名称,像JDBC那样resultSet.getString()、resultSet.getInt()获取到查询的值,动态设置到前面创建的空对象中;
  • 5、将此对象加入List结果集中,然后返回此集合或者集合的第一个元素;

那么Mybatis究竟是不是这样处理的呢,下面我们就来详细分析一下Mybatis是如何使用ResultSetHandler封装结果集的。

二、查询结果集封装流程

我们直接查看结果封装的开始入口:

代码语言:javascript
复制
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    return resultSetHandler.handleResultSets(ps);
}

由此,进入到 resultSetHandler.handleResultSets(ps) 方法,而默认会进入到的DefaultResultSetHandler的handleResultSets方法:

代码语言:javascript
复制
//org.apache.ibatis.executor.resultset.DefaultResultSetHandler#handleResultSets
public List<Object> handleResultSets(Statement stmt) throws SQLException {
  ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
    
  //存放查询结果的集合  
  final List<Object> multipleResults = new ArrayList<>();
  //结果集的个数  
  int resultSetCount = 0;
  //第一步:封装ResultSetWrapper
  ResultSetWrapper rsw = getFirstResultSet(stmt);
 
  //从mappedStatement中获取所有的ResultMap
  List<ResultMap> resultMaps = mappedStatement.getResultMaps();
  //resultMap的个数
  int resultMapCount = resultMaps.size();
  // 如果结果集有数据,但是没有定义返回的结果类型,就会报错
  //报错:A query was run and no Result Maps were found for the Mapped Statement..  It's likely that neither a Result Type nor a Result Map was specified.
  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.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++;
    }
  }
    
  //第四步:判断multipleResults结果集长度是不是等于1,如果等于1返回集合第一个元素,否则返回整个集合  
  return collapseSingleResultList(multipleResults);
}

上述代码有两个比较关键的地方:

  • 1、ResultSetWrapper rsw = getFirstResultSet(stmt):包装ResultSetWrapper,里面包含着columnNames、classNames、jdbcTypes等其他关键属性。
  • 2、handleResultSet(rsw, resultMap, multipleResults, null):处理结果集的详细过程,真正的处理结果集的地方;

下面分别对这两个步骤进行详细的介绍,先来看看ResultSetWrapper 是如何包装的,getFirstResultSet(stmt)里面主要还是调用了ResultSetWrapper的构造方法来创建一个ResultSetWrapper对象,所以我们直接看ResultSetWrapper的构造方法:

代码语言:javascript
复制
//结果集列名集合
private final List<String> columnNames = new ArrayList<>();
//结果集JavaType类型集合
private final List<String> classNames = new ArrayList<>();
//结果集JdbcType类型集合
private final List<JdbcType> jdbcTypes = new ArrayList<>();
 
public ResultSetWrapper(ResultSet rs, Configuration configuration) throws SQLException {
  super();
  this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
  this.resultSet = rs;
  //获取元数据
  final ResultSetMetaData metaData = rs.getMetaData();
  final int columnCount = metaData.getColumnCount();
  //挨个遍历所有的数据库列,添加到对应的集合中
  for (int i = 1; i <= columnCount; i++) {
    columnNames.add(configuration.isUseColumnLabel() ? metaData.getColumnLabel(i) : metaData.getColumnName(i));
    jdbcTypes.add(JdbcType.forCode(metaData.getColumnType(i)));
    classNames.add(metaData.getColumnClassName(i));
  }
}

如下图,可以看到,执行完ResultSetWrapper构造方法后,几个重要的属性的值如下所示:

图片.png
图片.png

接下来看另外一个关键的地方:handleResultSet(rsw, resultMap, multipleResults, null):执行具体的结果集封装操作

代码语言:javascript
复制
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 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()方法的源码看下如何处理每一行的值的:

代码语言:javascript
复制
//org.apache.ibatis.executor.resultset.DefaultResultSetHandler#handleRowValues
public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
  //判断是否有嵌套结果集
  if (resultMap.hasNestedResultMaps()) {
    ensureNoRowBounds();
    checkResultHandler();
    //处理含有嵌套ResultMap的结果
    handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
  } else {
    //此处示例我们没有嵌套结果集,所以执行的是else逻辑  
    handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
  }
}

本示例我们没有使用到嵌套结果集,所以执行的是else非嵌套逻辑:

代码语言:javascript
复制
private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
    throws SQLException {
  DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
  //获取ResultSet
  ResultSet resultSet = rsw.getResultSet();
  skipRows(resultSet, rowBounds);
  //主要判断上下文是否已关闭、resultSet是否关闭以及结果集是否还有元素
  while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
     ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
    //获取行数据
    Object rowValue = getRowValue(rsw, discriminatedResultMap, null);
    storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
  }
}

直接看Mybatis是如何获取一行数据的:

代码语言:javascript
复制
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
  final ResultLoaderMap lazyLoader = new ResultLoaderMap();
  //第一步:通过反射获取到需要封装的结果集实体类的构造方法,然后调用constructor.newInstance()创建一个对象
  //此时返回的rowValue: User{id='null', username='null'} 里面的属性都是null
  Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
  //行值不为空,并且结果对象有类型处理器
  if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
    //第二步:创建元数据对象MetaObject,方便后面直接设置属性的值
    //将行值包装成元数据对象MetaObject
    final MetaObject metaObject = configuration.newMetaObject(rowValue);
    boolean foundValues = this.useConstructorMappings;
    if (shouldApplyAutomaticMappings(resultMap, false)) {
      //第三步:自动映射查询出来的数据到前面创建好的Java对象属性中
      foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
    }
    foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
    foundValues = lazyLoader.size() > 0 || foundValues;
    rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
  }
  return rowValue;
}

跟踪applyAutomaticMappings,看一下Mybatis是如何自动映射查询出来的数据到前面创建好的Java对象属性中的:

代码语言:javascript
复制
private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
  //第一步:建立好数据库列名和实体类属性名的映射关系
  List<UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);
  boolean foundValues = false;
  if (!autoMapping.isEmpty()) {
    for (UnMappedColumnAutoMapping mapping : autoMapping) {
      //第二步:根据mapping.column数据库列名,从查询结果集中获取到具体某一列的值
      //底层实际上就是调用的TypeHandler的:rs.getString("id")、rs.getString("name")
      //感兴趣的小伙伴可以查看这个方法:org.apache.ibatis.type.StringTypeHandler#getNullableResult(java.sql.ResultSet, java.lang.String)
      final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);
      if (value != null) {
        foundValues = true;
      }
      if (value != null || (configuration.isCallSettersOnNulls() && !mapping.primitive)) {
        // gcode issue #377, call setter on nulls (value is not 'found')
        //第三步:拿到值之后,那就需要动态设置属性的值为刚刚获取到的值
        //通过metaObject元数据对象直接修改属性的值
        // 给对象的属性设置值
        metaObject.setValue(mapping.property, value);
      }
    }
  }
  return foundValues;
}

简单总结一下applyAutomaticMappings()方法,最主要的逻辑就在这里:

  • 第一步:建立好数据库列名和实体类属性名的映射关系

如下图,Mybatis建立好的数据库列名和实体类属性名的映射关系:

图片.png
图片.png
  • 第二步:根据mapping.column数据库列名,从查询结果集中获取到具体某一列的值
  • 第三步:拿到值之后,那就需要动态设置属性的值为刚刚获取到的值

如下图,可以看到,执行完第三步的时候,此时的结果集是下面这样的:

图片.png
图片.png

到这里,Mybatis查询结果集封装的步骤大体就完成了,接下来就是一级一级返回,添加到List结果集集合中,判断是返回一条数据还是直接返回整个结果集的集合。本文主要总结的是没有嵌套结果集的场景,感兴趣的小伙伴可以去看下嵌套结果集的处理流程,大体其实都是类似的,使用到了列名->属性映射关系,通过反射创建对象,拿到set方法,通过metaObject设置属性,无非可能会有一些集合的实例化等操作。

三、查询结果集封装流程

还是以一张流程图来总结一下查询结果集封装的过程:

图片.png
图片.png
四、总结

本篇文章详细总结了Mybatis查询结果集封装的整个流程,包括怎么建立数据库列名和实体类属性之间的映射、反射创建ResultType实体类对象、以及如何从结果集中拿到查询值,动态通过metaObject设置到返回类型实体类属性中等等。小伙伴们下去还是需要一步一步Debug调试一下,观察各个数据的流向和处理逻辑,这样效果可能会翻倍。

鉴于笔者水平有限,如果文章有什么错误或者需要补充的,希望小伙伴们指出来,希望这篇文章对大家有帮助。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-01-30,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、概述
  • 二、查询结果集封装流程
  • 三、查询结果集封装流程
  • 四、总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档