Mybatis源码解读系列(六)-ResultSetHandler

这一篇我们来梳理下Mybatis的ResultSetHandler接口及其DefaultResultSetHandler,这个接口是用来处理ResultSet结果集的。

一、ResultSetHandler接口

public interface ResultSetHandler {

  <E> List<E> handleResultSets(Statement stmt) throws SQLException;

  <E> Cursor<E> handleCursorResultSets(Statement stmt) throws SQLException;

  void handleOutputParameters(CallableStatement cs) throws SQLException;

}

​ 我们可以看到其这个接口有三个方法,下面两个是处理与存储过程先相关,我们主要梳理下第一个方法(其实它们在实现类中会调用一个底层处理的公共方法)。
​ 例如这里的第一个方法,其的入参是Statement,而出参是一个List的泛型集合,所以这个方法只要是遍历ResultSet将遍历结果封装到List中返回。现在我们来看下具体的实现,对于其的实现Mybatis只有一个类DefaultResultSetHandler。

二、DefaultResultSetHandler

1、结构&构造方法

public class DefaultResultSetHandler implements ResultSetHandler {

  public DefaultResultSetHandler(Executor executor, MappedStatement mappedStatement, ParameterHandler parameterHandler, ResultHandler<?> resultHandler, BoundSql boundSql,
                                   RowBounds rowBounds) {
      this.executor = executor;
      this.configuration = mappedStatement.getConfiguration();
      this.mappedStatement = mappedStatement;
      this.rowBounds = rowBounds;
      this.parameterHandler = parameterHandler;
      this.boundSql = boundSql;
      this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
      this.objectFactory = configuration.getObjectFactory();
      this.reflectorFactory = configuration.getReflectorFactory();
      this.resultHandler = resultHandler;
    }

​ 其实现ResultSetHandler接口,然后构造函数就是对其成员变量进行初始化赋值。

2、变量

1)、DEFERRED

private static final Object DEFERRED = new Object();

​ 这个就是改类创建的一个标识延迟处理的类(到源码分析的时候我们就知道其的作用了)。

2)、executor

private final Executor executor;

​ 执行器,这个我们前面有梳理。

3)、configuration

private final Configuration configuration;

​ 这个也有介绍,是存放Mybatis整个加载的内容。

4)、mappedStatement

private final MappedStatement mappedStatement;

​ 描叙Mapper接口其中方法执行需要的内容。

5)、rowBounds

private final RowBounds rowBounds;

​ 查询行数据处理。

6)、parameterHandler

private final ParameterHandler parameterHandler;

​ 参数处理器,之后再看需不需要具体分析。

7)、resultHandler

private final ResultHandler<?> resultHandler;

​ 结果处理器,这个也分析过来。

8)、boundSql

private final BoundSql boundSql;

​ sql脚本&执行相关描叙信息。

9)、typeHandlerRegistry

private final TypeHandlerRegistry typeHandlerRegistry;

​ 类型转换处理器。

10)、objectFactory

private final ObjectFactory objectFactory;

​ 根据类型创建对象的工厂。

11)、reflectorFactory

​ 创建根据类的Class创建对应的描叙去反射相关的内容,例如通过属性名去设置对应的值,就可以借助这个类。

12)、previousRowValue

private Object previousRowValue;

​ 前一个处理的数据行对象。

13)、nextResultMaps

private final Map<String, ResultMapping> nextResultMaps = new HashMap<>();

​ 这个是用来记录有多个返回映射的情况的。具体的我们到下面再具体分析。

14)、pendingRelations

private final Map<CacheKey, List<PendingRelation>> pendingRelations = new HashMap<>();

这个是用来处理延迟赋值的。

15)、useConstructorMappings

private boolean useConstructorMappings;
这个是用来表示可能会在<ResultMap>设置用构造方法去创建对象。

<resultMap id="StudentConstructorMap" type="org.apache.ibatis.submitted.extends_with_constructor.StudentConstructor">
    <constructor>
        <idArg column="id" javaType="int"/>
        <arg column="name" javaType="string"/>
    </constructor>
    <association property="teacher" column="teacher_id" select="org.apache.ibatis.submitted.extends_with_constructor.TeacherMapper.selectById"/>
</resultMap>
public StudentConstructor(Integer id, String name) {
  constructors.add(Constructor.ID_NAME);
  this.id = id;
  this.name = name;
}
public StudentConstructor(Integer id, String name) {
  constructors.add(Constructor.ID_NAME);
  this.id = id;
  this.name = name;
}

3、对于Result嵌套的介绍

​ 要梳理Mybatis对ResultSet 结果集的ResultMap嵌套处理,我们首先来看下Mybatis的xml文件是怎样写的。

<resultMap type="org.apache.ibatis.submitted.sptests.Item" id="itemResult">
  <result column="ID" property="id"/>
  <result column="ITEM" property="item"/>
</resultMap>

<resultMap type="org.apache.ibatis.submitted.sptests.Name" id="nameResultLinked">
  <result column="ID" property="id"/>
  <result column="FIRST_NAME" property="firstName"/>
  <result column="LAST_NAME" property="lastName"/>
  <collection property="items" column="id" foreignColumn="name_id" resultSet="items" resultMap="itemResult"/>
</resultMap>
<select id="getNamesAndItemsLinked" statementType="CALLABLE" resultSets="names,items" resultMap="nameResultLinked">
  {call sptest.getnamesanditems()}
</select>
create procedure sptest.getnamesanditems()
modifies sql data
dynamic result sets 2
BEGIN ATOMIC
  declare cur1 cursor for select * from sptest.names;
  declare cur2 cursor for select * from sptest.items;
  open cur1;
  open cur2;
END
go

可以看到这里是创建了一个存储过程,然后这个存储过程有两个结果集,对应的是[resultSets=“names,items”],这个items又体现在nameResultLinked 中的[resultSet=“items”]。但在这里我们是不是有一个疑问?names对应的是哪个?这是因为在第一个位置Mybatis是默认获取的,所以不需要映射。

public class Name {
  private Integer id;
  private String firstName;
  private String lastName;
  private List<Item> items;

​ 然后在Name中包含了Item集合,我们再来看下这个方法测试demo。

@Test
void testGetNamesAndItemsLinked() {
  try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
    SPMapper spMapper = sqlSession.getMapper(SPMapper.class);
    List<Name> names = spMapper.getNamesAndItemsLinked();
	assertEquals(4, names.size());
	assertEquals(2, names.get(0).getItems().size());
	assertEquals(1, names.get(1).getItems().size());
	assertNull(names.get(2).getItems());
	assertNull(names.get(3).getItems());
  }
}

4、方法

1)、整体介绍

@Override
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 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.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);
}

​ 下面我们就以为ResultSetHandler的handleResultSets(Statement stmt)为起点来分析下Mybatis对Result的处理映射创建对象的过程。
​ 这里整体逻辑是首先通过获取getFirstResultSet获取第一个ResultSet对应上面的ResultMap是[id=“nameResultLinked”],所以其会有四个试下Property。然后再通过mappedStatement.getResultMaps()获取遍历第一层的ResultMap(我们这里第一个只有一个),进入while循环然后通过handleResultSet方法去处理这层的结果集,将结果添加在resultMap中,这里的数据是没有处理给[ <collection property=“items” ]赋值的。
​ 之后通过getNextResultSet获取下一个ResultSet结果集的内容(注意这里并不是result.next,而是去获取下一个ResultSet)。由于[resultMapCount > resultSetCount]不满足,不继续本while的逻辑。所以目前的resultSetCount是1了,[ResultSetWrapper rsw]是另一个了。进入后一个while,这里是通过resultMaps.get(resultSetCount)获取,所以直接对应的就是[resultSet=“items”]了。再一次通过handleResultSet方法遍历赋值,只是这次parentMapping是有值的。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

​ 下面我们集体来分析下这里面调用的一些方法。

2)、getFirstResultSet(Statement stmt)

private ResultSetWrapper getFirstResultSet(Statement stmt) throws SQLException {
  ResultSet rs = stmt.getResultSet();
  while (rs == null) {
    // move forward to get the first resultset in case the driver
    // doesn't return the resultset as the first result (HSQLDB 2.1)
    if (stmt.getMoreResults()) {
      rs = stmt.getResultSet();
    } else {
      if (stmt.getUpdateCount() == -1) {
        // no more results. Must be no resultset
        break;
      }
    }
  }
  return rs != null ? new ResultSetWrapper(rs, configuration) : null;
}

​ 这个就是通过stmt.getResultSet()来获取第一个ResultSet然后将其包装为ResultSetWrapper。

3)、handleResultSet(…)

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());
  }
}

​ 这里是有两个分支的,第一次是没有parentMapping的,我们先看下面。这里是先判断resultHandler是不是为null是的话就创建一个默认的结果处理器。不过这里还是有不同,创建的默认是会将结果从defaultResultHandler中再添加到multipleResults中(目前就是这个分支)。

4)、handleRowValues

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);
  }
}

​ 这里首先是看这个resultMap是不是有嵌套的ResultMap,是的话就是这个分支。我们直接下一个分支handleRowValuesForSimpleResultMap方法。

5)、handleRowValuesForSimpleResultMap(…)

private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
    throws SQLException {
  DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
  ResultSet resultSet = rsw.getResultSet();
  skipRows(resultSet, rowBounds);
  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);
  }
}

​ 这里首先是通过skipRows进行ResultSet.absolute这种进行起始获取数据的开始节点。再通过shouldProcessMoreRows判断是不是已经将ResultSet中的结果获取了,这里通过resultSet.next()也往下移其实应该也是判断。然后通过getRowValue去获取该行数据并创建对应的对象。

6)、skipRows(ResultSet rs, RowBounds rowBounds)

private void skipRows(ResultSet rs, RowBounds rowBounds) throws SQLException {
  if (rs.getType() != ResultSet.TYPE_FORWARD_ONLY) {
    if (rowBounds.getOffset() != RowBounds.NO_ROW_OFFSET) {
      rs.absolute(rowBounds.getOffset());
    }
  } else {
    for (int i = 0; i < rowBounds.getOffset(); i++) {
      if (!rs.next()) {
        break;
      }
    }
  }
}

​ 可以看到这里如果是通过RowBounds去获取对应移动的数据的。同时如果不是ResultSet.TYPE_FORWARD_ONLY这种只能往前移,就是通过rs.absolute通过绝对位置进行设置。

7)、shouldProcessMoreRows

private boolean shouldProcessMoreRows(ResultContext<?> context, RowBounds rowBounds) {
  return !context.isStopped() && context.getResultCount() < rowBounds.getLimit();
}
public class DefaultResultContext<T> implements ResultContext<T> {

  private T resultObject;
  private int resultCount;
  private boolean stopped;

  public DefaultResultContext() {
    resultObject = null;
    resultCount = 0;
    stopped = false;
  }

​ 我们看到这里的DefaultResultContext其主要有两个参数,一个resultObject就是本行数据获取所对应的对象,而resultCount是累加来记录获取了多少条数据。

8)、getRowValue(…)

private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
  final ResultLoaderMap lazyLoader = new ResultLoaderMap();
  Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
  if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
    final MetaObject metaObject = configuration.newMetaObject(rowValue);
    boolean foundValues = this.useConstructorMappings;
    if (shouldApplyAutomaticMappings(resultMap, false)) {
      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;
}

​ 这里就是去获取对应行的数据了。通过createResultObject创建记录对应的被赋值的对象,在通过applyAutomaticMappings&applyPropertyMappings来对该对象进行赋值,这里得applyAutomaticMappings是对自动映射的属性进行赋值,applyPropertyMappings是对你写在<ResultMap>中的内容进行赋值。这个方法里面就不具体展开了。但我们佐证下前面提到的不好给第四个items属性赋值,这里看下其调用链中的一个方法getPropertyMappingValue(…)。

9)、getPropertyMappingValue

private Object getPropertyMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix)
    throws SQLException {
  if (propertyMapping.getNestedQueryId() != null) {
    return getNestedQueryMappingValue(rs, metaResultObject, propertyMapping, lazyLoader, columnPrefix);
  } else if (propertyMapping.getResultSet() != null) {
    addPendingChildRelation(rs, metaResultObject, propertyMapping);   // TODO is that OK?
    return DEFERRED;
  } else {
    final TypeHandler<?> typeHandler = propertyMapping.getTypeHandler();
    final String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
    return typeHandler.getResult(rs, column);
  }
}
private void addPendingChildRelation(ResultSet rs, MetaObject metaResultObject, ResultMapping parentMapping) throws SQLException {
   ......
  List<PendingRelation> relations = pendingRelations.computeIfAbsent(cacheKey, k -> new ArrayList<>());
  // issue #255
  relations.add(deferLoad);
  ResultMapping previous = nextResultMaps.get(parentMapping.getResultSet());
  if (previous == null) {
    nextResultMaps.put(parentMapping.getResultSet(), parentMapping);
  } else {
    if (!previous.equals(parentMapping)) {
      throw new ExecutorException("Two different properties are mapped to the same resultSet");
    }
  }
}

​ 例如这里[ propertyMapping.getResultSet() != null ],就会进入该分支然后将其添加到pendingRelations。而不是进入下面的else去进行对应的赋值。

在这里插入图片描述

10)、storeObject(…)

private void storeObject(ResultHandler<?> resultHandler, DefaultResultContext<Object> resultContext, Object rowValue, ResultMapping parentMapping, ResultSet rs) throws SQLException {
  if (parentMapping != null) {
    linkToParents(rs, parentMapping, rowValue);
  } else {
    callResultHandler(resultHandler, resultContext, rowValue);
  }
}

​ 这个方法是在前面handleRowValuesForSimpleResultMap获取对应行数据后调用的,有parentMapping就表示是嵌套的ResultMap。例如我们前面介绍的[items]有提到,在最初只会对前三个属性赋值,第四个还没有赋值,对于第4个的赋值,就通过这个linkToParents方法另将去赋值到其对应的parent记录中,通过上面的pendingRelations来找到对应的父类,就如同它的名称linkToParents一样。当然第一次是没有这个的,所以第一次是先将该条记录通过callResultHandler方法添加到前面讲的DefaultResultHandler中。

11)、callResultHandler

private void callResultHandler(ResultHandler<?> resultHandler, DefaultResultContext<Object> resultContext, Object rowValue) {
  resultContext.nextResultObject(rowValue);
  ((ResultHandler<Object>) resultHandler).handleResult(resultContext);
}
public void nextResultObject(T resultObject) {
  resultCount++;
  this.resultObject = resultObject;
}
@Override
public void handleResult(ResultContext<?> context) {
  list.add(context.getResultObject());
}

这个方法就说明了在前面描述中所说的一些内容。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值