1.简介
ResultSetHandler是mybatis的一个·核心组件,作用是完成结果集的封装处理和存储过程的输出。一个典型的应用是在statementHandler中完成query时,对结果集进行封装处理。
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
//转ps
PreparedStatement ps = (PreparedStatement)statement;
//执行sql语句并将结果以字节数组的形式存放在ps.results.rowData中
ps.execute();
// ResultSetHandler解析ps内的结果集,封装结果集为指定的resultMap类型并将其返回
return this.resultSetHandler.handleResultSets(ps);
}
2.结果集处理
在讲解resultSetHandler.handleResultSets()之前,我们先来看看要处理的数据是什么样子的,想要得到的数据类型又是什么样的。然后再来讲它是如何进行封装处理的。
- 前置过程:在resultHandler进行结果集处理之前,必然要有一处操作能获取到结果集。在上面的例子中,是ps.execute()方法。它在执行之后就从数据库查询到了结果集。这条statement对应的sql语句如下: 逻辑是:给一个List<String> articleIds, 查询这些文章的分享次数和所有的分享人信息,最终获得一个ArrayList.
ArrayList<Integer> articleIds = new ArrayList<>(Arrays.asList(a));
List<ArticleShareStaticsInfoVo> infos = mapper.getAllArticleShareStatics(articleIds);
mapper文件的resultMap和select语句如下:
<resultMap id="articleShareInfoStaticsVoMap" type="org.orange.bean.vo.ArticleShareStaticsInfoVo">
<id column="article_id" property="articleId"></id>
<result column="count" property="shareCount"></result>
<collection property="sharedBy" ofType="org.orange.entity.UserEntity">
<id column="user_id" property="userId"></id>
<result column="user_name" property="userName"></result>
<result column="user_age" property="userAge"></result>
</collection>
</resultMap>
<select id="getAllArticleShareStatics" resultMap="articleShareInfoStaticsVoMap">
select a.article_id, u.user_id, u.user_name, u.user_age, t2.count
from article_share_info a
left join user_info u
on a.shared_by = u.user_id
left join
(select article_id, count(1) as count from article_share_info where article_id in
<foreach collection="articleIds" item="item" open="(" close=")" index="index" separator=",">
#{item}
</foreach>
group by article_id) t2
on a.article_id = t2.article_id
where a.article_id in
<foreach collection="articleIds" item="item" open="(" close=")" index="index" separator=",">
#{item}
</foreach>
</select>
查询结果如下
[ArticleShareStaticsInfoVo{articleId=1, shareCount=2, sharedBy=[UserEntity{userId=222, userAge=23, userName='rual'}, UserEntity{userId=333, userAge=25, userName='zidane'}]},
ArticleShareStaticsInfoVo{articleId=2, shareCount=3, sharedBy=[UserEntity{userId=111, userAge=22, userName='owen'}, UserEntity{userId=222, userAge=23, userName='rual'}, UserEntity{userId=333, userAge=25, userName='zidane'}]}]
在ps.execute()之后,实际上就已经从数据库中得到了结果集,并封装到了ps.results中。我们一共查出了5条记录,每条记录有5列,分别是article_id, count, u_id, u_name, u_age,如下图所示。rows中有5个元素,他们对应5条记录。每条记录中有一个长度为5的字节数组 ,数组的每个元素对应一列的值。metadata中包含了每一列的jdbc信息。将他们排列成二维,不就构成了原始的结果集嘛。
每行的数据和每列的field
在这里,有一个很直观的想法:把rows中每一行记录,都由jdbc转成java类型,那么就得到了对应的数据。可是存在两个问题。
- 它们对应的java类型是什么?
- 我本来想要的结果是这样的: 每篇文章对应一个shareVo, 该vo中包含了文章id,分享次数、分享人UserEntity组成的ArrayList,按上面的想法不能够把uid,uname,uage封装到UserEntity中。
事实上,我们早已经在resultMap中定义了返回什么样的数据了。同时,这个resultMap里也包含了一个嵌套的结果集:Userentity: (uid,uname,uage)。
那很显然,要处理结果集,除了要利用结果集之外,还要利用resultMap.
讲到这里,再来看handleResultSets就很好理解了,话不多说,直接上代码。
public List<Object> handleResultSets(Statement stmt) throws SQLException {
ErrorContext.instance().activity("handling results").object(this.mappedStatement.getId());
List<Object> multipleResults = new ArrayList();
int resultSetCount = 0;
// 一条sql语句是可以返回多个结果集的,不懂可以去查一查。上面举得例子只有1个。
// 将结果集与configuration封装到rsw内。
ResultSetWrapper rsw = this.getFirstResultSet(stmt);
// 获取到resultMaps,同上,如果一条sql有多个结果集,那么可以给他多个resultMap来封装。
// 这条语句只有1个resultMap: articleShareInfoStaticsVoMap
List<ResultMap> resultMaps = this.mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
this.validateResultMapsCount(rsw, resultMapCount);
//为每个结果集进行封装。
while(rsw != null && resultMapCount > resultSetCount) {
// 获取这个结果集对应的resultMap。
ResultMap resultMap = (ResultMap)resultMaps.get(resultSetCount);
// 重点在这。
this.handleResultSet(rsw, resultMap, multipleResults, (ResultMapping)null);
//获取下一个结果集,继续封装。由于本例中只有1个结果集,下一次循环rsw就为null了
rsw = this.getNextResultSet(stmt);
this.cleanUpAfterHandlingResultSet();
++resultSetCount;
}
String[] resultSets = this.mappedStatement.getResultSets();
// 如果statement中含有结果集,处理流程和上面大同小异
if (resultSets != null) {
while(rsw != null && resultSetCount < resultSets.length) {
ResultMapping parentMapping = (ResultMapping)this.nextResultMaps.get(resultSets[resultSetCount]);
if (parentMapping != null) {
String nestedResultMapId = parentMapping.getNestedResultMapId();
ResultMap resultMap = this.configuration.getResultMap(nestedResultMapId);
this.handleResultSet(rsw, resultMap, (List)null, parentMapping);
}
rsw = this.getNextResultSet(stmt);
this.cleanUpAfterHandlingResultSet();
++resultSetCount;
}
}
//如果只有1个结果集,那么就返回它对应的arrayList(即multipleResults[0]),有多个则返回multipleResults
return this.collapseSingleResultList(multipleResults);
}
这个方法的逻辑还是符合我们思路的:获取结果集和对应的resultMap,完成封装。
封装的过程还没说,如何进行嵌套处理也不知道,且看handleResultSet(rsw, resultMap, multipleResults, (ResultMapping)null)方法是怎么做的吧。
private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
try {
if (parentMapping != null) {
this.handleRowValues(rsw, resultMap, (ResultHandler)null, RowBounds.DEFAULT, parentMapping);
} else if (this.resultHandler == null) {
DefaultResultHandler defaultResultHandler = new DefaultResultHandler(this.objectFactory);
//对每一行结果进行处理
this.handleRowValues(rsw, resultMap, defaultResultHandler, this.rowBounds, (ResultMapping)null);
multipleResults.add(defaultResultHandler.getResultList());
} else {
this.handleRowValues(rsw, resultMap, this.resultHandler, this.rowBounds, (ResultMapping)null);
}
} finally {
this.closeResultSet(rsw.getResultSet());
}
}
创建defaultResultHandler,对rsw逐行处理。还得进入handlerRowValues方法继续刨根问底。
public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
// 如果当前resultMap中有嵌套的resultMap, 现在我们resultMap中嵌套了一个collection,显然是有的
if (resultMap.hasNestedResultMaps()) {
//先保证没有分页
this.ensureNoRowBounds();
this.checkResultHandler();
// 逐行处理, 得把部分数据封装到嵌套的resultMap指定的类型。
this.handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
} else {
//没有嵌套的resultMap,逐行处理,完成封装就行了
this.handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
}
}
有嵌套的话,如下处理:
private void handleRowValuesForNestedResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
DefaultResultContext<Object> resultContext = new DefaultResultContext();
ResultSet resultSet = rsw.getResultSet();
this.skipRows(resultSet, rowBounds);
Object rowValue = this.previousRowValue;
// 遍历结果集每一行
while(this.shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
// 解析resultMap,创建discriminatedResultMap。它的作用就是完成区分。虽然我查出了5条记录,但是最终按照articleId区分,封装成了2个元素。
ResultMap discriminatedResultMap = this.resolveDiscriminatedResultMap(resultSet, resultMap, (String)null);
//这一行的key, 用来判断是否应该将多条包含嵌套的结果集合并到一个对象内
CacheKey rowKey = this.createRowKey(discriminatedResultMap, rsw, (String)null);
//第一次进来nestedResultObjects还是null, 此时partialObject也为null.
//用于记录之前这一个key所创建的对象,如果前面创建了的话,那么后续就只需要创建嵌套在内层的对象了,如UserEntity,然后注入到sharedBy内。
Object partialObject = this.nestedResultObjects.get(rowKey);
if (this.mappedStatement.isResultOrdered()) {
if (partialObject == null && rowValue != null) {
this.nestedResultObjects.clear();
this.storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
}
rowValue = this.getRowValue(rsw, discriminatedResultMap, rowKey, (String)null, partialObject);
} else {
// 逐行获取
rowValue = this.getRowValue(rsw, discriminatedResultMap, rowKey, (String)null, partialObject);
if (partialObject == null) {
this.storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
}
}
}
if (rowValue != null && this.mappedStatement.isResultOrdered() && this.shouldProcessMoreRows(resultContext, rowBounds)) {
this.storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
this.previousRowValue = null;
} else if (rowValue != null) {
this.previousRowValue = rowValue;
}
}
在这里贴一个descrinatedResultMapper的图
descrinatedResultMapper
可以看到,u_id,uname,uage都被sharedBy给替换掉了。
该方法重点在于getRowValue,将一行数据封装成一个xxxVoMap,入参就是上面这个discriminatedResultMap
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, CacheKey combinedKey, String columnPrefix, Object partialObject) throws SQLException {
// 获取resuleMap的id, 即xml配置中写的<resultMap id ="xxxMap" ...>
String resultMapId = resultMap.getId();
Object rowValue = partialObject;
// 如果这一行的discriminatedResultMap对应的对象已经被创建过了,那么只需要为他处理嵌套的resultMap就好了
if (partialObject != null) {
MetaObject metaObject = this.configuration.newMetaObject(partialObject);
this.putAncestor(partialObject, resultMapId);
this.applyNestedResultMappings(rsw, resultMap, metaObject, columnPrefix, combinedKey, false);
this.ancestorObjects.remove(resultMapId);
} else {
ResultLoaderMap lazyLoader = new ResultLoaderMap();
// 创建封装结果的对象
// 第一次进来时,比如第一行的结果,{articleId=0, shareCount=0, sharedBy=null},此时仅仅完成了对象的初始化。
//如果在这行之前已经创建过了,但是还有其他的行记录也要被封装到这个对象内,被存储在partialObject,此时,rowValue={articleId=1, shareCount=2, sharedBy=[UserEntity{uId = 222, uName=rual, uage=23}, ...]},那么就不需要创建,而是走上面的if分支
//如果是内嵌的结果,第一次进来创建一个初始的对象,对象field都还是默认值
rowValue = this.createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
if (rowValue != null && !this.hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
MetaObject metaObject = this.configuration.newMetaObject(rowValue);
boolean foundValues = this.useConstructorMappings;
if (this.shouldApplyAutomaticMappings(resultMap, true)) {
foundValues = this.applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
}
// property映射, 映射完成之后, properties对应的field就不是对象的初始值了。
foundValues = this.applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
this.putAncestor(rowValue, resultMapId);
// 为嵌套的结果集创建对象,在该方法中又要获取嵌套的nestedResultMap, 然后对该行数据调用一次getRowValue,从而获取到嵌套的对象对应的值,并创建对象。
foundValues = this.applyNestedResultMappings(rsw, resultMap, metaObject, columnPrefix, combinedKey, true) || foundValues;
this.ancestorObjects.remove(resultMapId);
foundValues = lazyLoader.size() > 0 || foundValues;
rowValue = !foundValues && !this.configuration.isReturnInstanceForEmptyRow() ? null : rowValue;
}
if (combinedKey != CacheKey.NULL_CACHE_KEY) {
this.nestedResultObjects.put(combinedKey, rowValue);
}
}
return rowValue;
}
讲一下该方法的执行流程;
还是以ArticleShareStatisticsVo来说,它在sharedBy内嵌套了一个UserEntity构成的list。
方法进来时,如果还没有对article_id = 1, count =2的记录创建articleShareStatisticsvo对象时,即此时partialObject = null, 那么要先创建一个对象,然后applyProperties为该对象赋值。
比如第一行数据是这样的(只是打个比方,实际应该是rsw中results的样子){article_id:1, count: 2, user_id: 111, user_name: rual, user_age: 23}, 首先处理的是discriminatedResultMap, 即articleid, shareCount, sharedBy三个属性。其中sharedBy又由嵌套的resultMap来处理。
所以进来先创建rowValue 为ArtricleShareStaticsInfoVo,并为他赋值。(create + applyProperties)
rowValue
然后进入到applyNestedResultMappings的时候,仍然是处理这一行,但此时要对嵌套的resultMap进行处理。
rowValue = this.getRowValue(rsw, nestedResultMap, combinedKey, columnPrefix, rowValue);
嵌套处理的时候,内层同样要调用getRowValue方法, 在创建UserEntity.
sharedBy中内嵌的UserEntity
如果还有嵌套,那么在getRowValue中又要钓applyNestedResultMappings()方法。依次递归,最终将所有嵌套的结果集处理完成,就将一行数据封装完成了。
如果有另外一行articleId= 1,count =2的记录,分享人是齐达内。之前获取到劳尔对它的分享记录时,对这篇文章已经创建过对象了。在这次循环中,partialObject就是前面创建的这个对象,那么走了下面这个分支。此时不必再创建新的articleShareStaticsVo对象,而是applyNestedResultMappings(... newObject:false),此时只需要对齐达内创建UserEntity对象,然后放入到sharedBy属性中即可。
image.png
网友评论