这一篇我们来梳理下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());
}
这个方法就说明了在前面描述中所说的一些内容。