Mybatis源碼閱讀(三):結果集映射3.2 —— 嵌套映射

前言

這段時間疫情原因躺在家做鹹魚,代碼也沒怎麼敲,源碼也沒怎麼看,博客拖更了一個月,今天心血來潮繼續讀了點源碼,晚上正好抽空發個博客,證明我還活着。

關於結果集映射,在一個月前的博客中已經將簡單映射給講述完畢,在實際應用中,除了單表查詢以外,還可能通過連表查詢多張表的記錄,這些記錄需要映射成多個java對象,而對象之間存在一對一、一對多等複雜的關聯關係,這時候就需要嵌套映射。

handleRowValues

在前面的一篇博客中提到,結果集映射的核心方法是handleRowValues,在這個方法中,會先判斷ResultMap是否存在嵌套映射,如不存在就視爲簡單結果集映射,簡單映射的處理在上一篇博客已經講解完畢,本篇博客講述的是嵌套映射

    /**
     * 結果集映射核心方法
     *
     * @param rsw
     * @param resultMap
     * @param resultHandler
     * @param rowBounds
     * @param parentMapping
     * @throws SQLException
     */
    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);
        }
    }

handleRowValuesForNestedResultMap

該方法是處理嵌套映射的核心方法,有以下主要步驟

  1. 通過skipRows方法定位到指定的記錄行
  2. 通過shouldProcessMoreRows方法檢測是否能夠繼續映射結果集中剩餘的記錄行
  3. 調用resolveDiscriminatedResultMap方法,根據ResultMap中記錄的Discriminator對象以及參與映射的記錄行中相應的列值,決定映射使用的ResultMap對象。
  4. 通過createRowKey方法爲該行記錄生成CacheKey,CacheKey作爲緩存中的key值,同時在嵌套映射中也作爲key唯一標識一個結果集對象。
  5. 根據上面步驟生成的CacheKey查詢DefaultRe.nestedResultObjects集合,這個字段是一個HashMap,在處理嵌套映射過程中生成的所有結果對象,都會生成相應的CacheKey並保存到該集合。
  6. 檢測<select>節點中resultOrdered屬性的配置,該設置僅對嵌套映射有效。當Ordered屬性爲true時,則認爲返回一個主結果行
  7. 通過getRowValue,完成當前記錄行的映射操作並返回結果對象,其中還會講結果對象添加到nestedResultObjects集合中。
  8. 通過storeObject方法將生成的結果對象保存在ResultHandler中。

handleRowValuesForNestedResultMap方法代碼如下。、

    /**
     * 處理嵌套映射
     * @param rsw
     * @param resultMap
     * @param resultHandler
     * @param rowBounds
     * @param parentMapping
     * @throws SQLException
     */
    private void handleRowValuesForNestedResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
        final DefaultResultContext<Object>  resultContext = new DefaultResultContext<>();
        // 獲取結果集
        ResultSet resultSet = rsw.getResultSet();
        // 定位到指定的行
        skipRows(resultSet, rowBounds);
        Object rowValue = previousRowValue;
        // 檢測在定位到指定行之後,是否還有需要映射的數據
        while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
            // 得到本次查詢使用的ResultMap
            final ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
            // 爲該行記錄生成CacheKey,作爲緩存中的key值
            final CacheKey rowKey = createRowKey(discriminatedResultMap, rsw, null);
            // 根據緩存key先獲取映射緩存
            Object partialObject = nestedResultObjects.get(rowKey);
            // 檢測select節點中的resultOrder屬性。該屬性只針對嵌套映射有效。
            // 當true時則認爲返回一個主結果行時,不會記錄nestedResultObject
            if (mappedStatement.isResultOrdered()) {
                // 主結果對象發生變化
                if (partialObject == null && rowValue != null) {
                    // 清空緩存集合
                    nestedResultObjects.clear();
                    // 保存主結果對象
                    storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
                }
                // 獲取映射結果
                rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject);
            } else {
                rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject);
                if (partialObject == null) {
                    // 將生成結果保存到ResultHandler
                    storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
                }
            }
        }
        if (rowValue != null && mappedStatement.isResultOrdered() && shouldProcessMoreRows(resultContext, rowBounds)) {
            storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
            previousRowValue = null;
        } else if (rowValue != null) {
            previousRowValue = rowValue;
        }
    }

前面一部分代碼的分析在簡單映射中已經描述過,不記得的朋友可以查看一下上一篇源碼閱讀文章,這裏從createRowKey方法開始。

createRowKey

createRowKey方法主要負責生成CacheKey,該方法構建CacheKey的過程如下。

  1. 嘗試使用<idArg>節點或者<id>節點中定義的列名以及該列在當前記錄行中對應的列值生成CacheKey
  2. 如果ResultMap中沒有定義這兩個節點,則有ResultMap中明確要映射的列名以及它們在當前記錄行中對應的列值一起構成CacheKey對象
  3. 經過上面兩個步驟後如果依然查不到相關的列名和列值,且ResultMap的type屬性明確指明瞭結果對象爲Map類型,則有結果集中所有列名以及改行記錄行的所有列值一起構成CacheKey
  4. 如果映射的結果對象不是Map,則由結果集中未映射的列名以及它們在當前記錄行中的對應列值一起構成CacheKey

createRowKey代碼如下

    /**
     * 創建一個CacheKey,作爲緩存中的key值,在嵌套映射中也作爲key唯一標識一個結果對象
     * @param resultMap
     * @param rsw
     * @param columnPrefix
     * @return
     * @throws SQLException
     */
    private CacheKey createRowKey(ResultMap resultMap, ResultSetWrapper rsw, String columnPrefix) throws SQLException {
        final CacheKey cacheKey = new CacheKey();
        // 將resultMap的id屬性作爲CacheKey的一部分
        cacheKey.update(resultMap.getId());
        // 查找ResultMapping集合
        List<ResultMapping> resultMappings = getResultMappingsForRowKey(resultMap);
        // 沒找到
        if (resultMappings.isEmpty()) {
            if (Map.class.isAssignableFrom(resultMap.getType())) {
                // 由結果集中的所有列名以及當前記錄行的所有列值一起構成CacheKey
                createRowKeyForMap(rsw, cacheKey);
            } else {
                // 由結果集中未映射的列名以及它們在當前記錄行中的對應列值一起構成CacheKey對象
                createRowKeyForUnmappedProperties(resultMap, rsw, cacheKey, columnPrefix);
            }
        } else {
            // 由ResultMapping集合中的列名以及它們在當前記錄行中相應的列值一起構成CacheKey
            createRowKeyForMappedProperties(resultMap, rsw, cacheKey, resultMappings, columnPrefix);
        }
        // 如果在上面的過程沒有找到任何列參與構成CacheKey對象,則返回NullCacheKey
        if (cacheKey.getUpdateCount() < 2) {
            return CacheKey.NULL_CACHE_KEY;
        }
        return cacheKey;
    }

其中,getResultMappingsForRowKey方法首先檢查ResultMap中是否定義了idArg或者id節點,如果是則返回idResultMappings集合,否則返回propertyResultMappings集合

    /**
     * 獲取ResultMapping集合
     * @param resultMap
     * @return
     */
    private List<ResultMapping> getResultMappingsForRowKey(ResultMap resultMap) {
        //首先檢查resultMap中是否定義了idArg節點或者id節點
        List<ResultMapping> resultMappings = resultMap.getIdResultMappings();
        if (resultMappings.isEmpty()) {
            // propertyResultMappings集合記錄了除id和constructor節點以外的ResultMapping對象
            resultMappings = resultMap.getPropertyResultMappings();
        }
        return resultMappings;
    }

createRowKeyForMap、createRowKeyForUnmappedProperties和createRowKeyForMappedProperties三個方法核心邏輯都是通過CacheKey的update方法,將指定的列名以及它們在當前記錄行中相應的列值添加到CacheKey,使之成爲CacheKey對象的一部分。

這裏只介紹createRowKeyForMappedProperties

    /**
     * 核心邏輯是通過CacheKey.update方法,將指定的列名以及它們在當前記錄行中相應的列值添加到CacheKey
     * @param resultMap
     * @param rsw
     * @param cacheKey
     * @param resultMappings
     * @param columnPrefix
     * @throws SQLException
     */
    private void createRowKeyForMappedProperties(ResultMap resultMap, ResultSetWrapper rsw, CacheKey cacheKey, List<ResultMapping> resultMappings, String columnPrefix) throws SQLException {
        for (ResultMapping resultMapping : resultMappings) {
            // 如果存在嵌套映射,並且resultSet不爲空
            if (resultMapping.getNestedResultMapId() != null && resultMapping.getResultSet() == null) {
                // 如果存在嵌套映射,遞歸調用該方法處理
                final ResultMap nestedResultMap = configuration.getResultMap(resultMapping.getNestedResultMapId());
                createRowKeyForMappedProperties(nestedResultMap, rsw, cacheKey, nestedResultMap.getConstructorResultMappings(),
                        prependPrefix(resultMapping.getColumnPrefix(), columnPrefix));
            } else if (resultMapping.getNestedQueryId() == null) {
                // 忽略嵌套查詢
                // 獲取該列名稱
                final String column = prependPrefix(resultMapping.getColumn(), columnPrefix);
                // 獲取該列相應的TypeHandler
                final TypeHandler<?> th = resultMapping.getTypeHandler();
                // 獲取映射的列名
                List<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
                if (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH))) {
                    // 獲取列值
                    final Object value = th.getResult(rsw.getResultSet(), column);
                    if (value != null || configuration.isReturnInstanceForEmptyRow()) {
                        // 將列值和列名添加到CacheKey中
                        cacheKey.update(column);
                        cacheKey.update(value);
                    }
                }
            }
        }
    }

getRowValue方法

getRowValue方法主要負責對數據集中的一行記錄進行映射。在處理嵌套映射的過程中,會調用getRowValue方法,完成對記錄行的映射,步驟如下。

檢測外層對象是否已經存在

如果外層對象不存在

  1. 調用createRowObject方法創建外層對象
  2. 將外層對象添加到DefaultResultSetHandler.ancestorObject集合中,其中key是ResultMap的id,value爲外層對象。
  3. 通過通過applyNestedResultMappings方法處理嵌套映射,其中會將生成的結果對象設置到外層對象的相應的屬性中。
  4. 將外層的ancestorObject集合中移除
  5. 將外層對象保存到nestedResultObjects集合中。

如果外層對象已存在

  1. 將外層對象添加到ancestorObjects集合中
  2. 通過applyNestedResultMappings方法處理嵌套映射,其中會將生成的結果對象設置到外層對象的相應屬性中
  3. 將外層對象從ancestorObjects集合中移除。

getRowValue方法代碼如下


    /**
     * 完成對嵌套查詢記錄的映射
     * @param rsw
     * @param resultMap
     * @param combinedKey
     * @param columnPrefix
     * @param partialObject
     * @return
     * @throws SQLException
     */
    private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, CacheKey combinedKey, String columnPrefix, Object partialObject) throws SQLException {
        final String resultMapId = resultMap.getId();
        Object rowValue = partialObject;
        if (rowValue != null) {
            // 外層對象存在
            final MetaObject metaObject = configuration.newMetaObject(rowValue);
            // 將外層對象添加到ancestorObjects
            putAncestor(rowValue, resultMapId);
            // 處理嵌套映射,其中會將生成的結果對象設置到外層對象的相應屬性中
            applyNestedResultMappings(rsw, resultMap, metaObject, columnPrefix, combinedKey, false);
            // 將外層對象從ancestorObjects移除
            ancestorObjects.remove(resultMapId);
        } else {
            // 外層對象不存在
            final ResultLoaderMap lazyLoader = new ResultLoaderMap();
            // 創建外層對象
            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, true)) {
                    // 自動映射
                    foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
                }
                // 處理ResultMap找那個明確需要映射的列
                foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
                putAncestor(rowValue, resultMapId);
                // 處理嵌套映射,將生成的結果對象設置到外層對象的相應的屬性中
                foundValues = applyNestedResultMappings(rsw, resultMap, metaObject, columnPrefix, combinedKey, true) || foundValues;
                // 將外層對象從ancestorObjects集合中移除
                ancestorObjects.remove(resultMapId);
                foundValues = lazyLoader.size() > 0 || foundValues;
                rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
            }
            if (combinedKey != CacheKey.NULL_CACHE_KEY) {
                // 將外層對象添加到nestedResultObjects
                nestedResultObjects.put(combinedKey, rowValue);
            }
        }
        return rowValue;
    }

applyNestedResultMappings方法

處理嵌套邏輯的核心在這個方法中,該方法會遍歷ResultMap.propertyResultMappings集合中記錄的ResultMapping對象,並處理其中的嵌套映射。該方法步驟如下。

  1. 獲取ResultMapping.nestedResultMapId字段值,該值不爲空則表示存在相應的嵌套映射要處理。同時還會檢測ResultMapping.resultSet字段,它指定了要映射的結果及名稱,該屬性的映射在前面的handleResultSets方法中完成。
  2. 通過resolveDiscriminatedResultMap方法確定嵌套映射使用的ResultMap對象
  3. 處理循環引用的場景,如果不存在循環引用的情況,則繼續後面的映射流程。如果存在循環引用,則不在創建新的對象,而是重用前面的對象
  4. 通過createRowKey方法爲嵌套對象創建CacheKey。該過程除了根據嵌套對象的信息創建CacheKey,還會與外層對象的CacheKey合併,得到全局唯一的CacheKey
  5. 如果外層對象中用於記錄當前嵌套對象的屬性爲Collection並且未初始化,則會通過instantiateCollectionPropertyIfAppropriate方法初始化該對象
  6. 根據association、collection等節點的notNullColumn屬性,檢測結果集中相應的列是否爲空
  7. 調用getRowValue方法完成嵌套映射,並生成嵌套對象。嵌套對象可以嵌套多層,也就可以產生多層遞歸。
  8. 通過linkObjects方法,將上一步驟得到的嵌套對象保存到外層對象。

applyNestedResultMappings方法代碼如下

    /**
     * 處理嵌套映射的核心代碼
     * @param rsw
     * @param resultMap
     * @param metaObject
     * @param parentPrefix
     * @param parentRowKey
     * @param newObject
     * @return
     */
    private boolean applyNestedResultMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String parentPrefix, CacheKey parentRowKey, boolean newObject) {
        boolean foundValues = false;
        for (ResultMapping resultMapping : resultMap.getPropertyResultMappings()) {
            // 獲取引用其他的ResultMap的id
            final String nestedResultMapId = resultMapping.getNestedResultMapId();
            // 如果指定了嵌套映射的id,並且尚未映射
            if (nestedResultMapId != null && resultMapping.getResultSet() == null) {
                try {
                    // 獲取列前綴
                    final String columnPrefix = getColumnPrefix(parentPrefix, resultMapping);
                    // 根據上面獲取到的嵌套映射id去從配置中找到對應的ResultMap
                    final ResultMap nestedResultMap = getNestedResultMap(rsw.getResultSet(), nestedResultMapId, columnPrefix);
                    // 列前綴爲空的情況下處理,一般不去用
                    if (resultMapping.getColumnPrefix() == null) {
                        Object ancestorObject = ancestorObjects.get(nestedResultMapId);
                        if (ancestorObject != null) {
                            if (newObject) {
                                linkObjects(metaObject, resultMapping, ancestorObject);
                            }
                            continue;
                        }
                    }
                    // 爲嵌套對象創建CacheKey,該過程創建的CacheKey還會與外層對象的CacheKey合併
                    final CacheKey rowKey = createRowKey(nestedResultMap, rsw, columnPrefix);
                    // 合併CacheKey
                    final CacheKey combinedKey = combineKeys(rowKey, parentRowKey);
                    Object rowValue = nestedResultObjects.get(combinedKey);
                    boolean knownValue = rowValue != null;
                    // 如果嵌套對象是集合,並且沒有初始化,會調用該方法對其進行初始化
                    instantiateCollectionPropertyIfAppropriate(resultMapping, metaObject);
                    // 根據notNullColumn屬性,檢測結果集中相應的列是否爲空
                    if (anyNotNullColumnHasValue(resultMapping, columnPrefix, rsw)) {
                        // 獲取映射結果
                        rowValue = getRowValue(rsw, nestedResultMap, combinedKey, columnPrefix, rowValue);
                        if (rowValue != null && !knownValue) {
                            linkObjects(metaObject, resultMapping, rowValue);
                            foundValues = true;
                        }
                    }
                } catch (SQLException e) {
                    throw new ExecutorException("Error getting nested result map values for '" + resultMapping.getProperty() + "'.  Cause: " + e, e);
                }
            }
        }
        return foundValues;
    }

結語

距離上一篇源碼分析的博客已經間隔了一個多月,最近在家閒夠了就着手繼續寫博客了,關於這塊的內容不會棄坑,只是偶爾會拖更一下下。。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章