美文网首页
mybatis运行原理06- ResultSetHandler

mybatis运行原理06- ResultSetHandler

作者: 布拉德老瓜 | 来源:发表于2021-03-22 22:04 被阅读0次

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类型,那么就得到了对应的数据。可是存在两个问题。

    1. 它们对应的java类型是什么?
    1. 我本来想要的结果是这样的: 每篇文章对应一个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

相关文章

网友评论

      本文标题:mybatis运行原理06- ResultSetHandler

      本文链接:https://www.haomeiwen.com/subject/dqethltx.html