Mybatis源碼閱讀(三):結果集映射3.1 —— ResultSetBuilder與簡單映射

前言

在前面的文章中,已經介紹了三種SqlSource的代碼,下面開始介紹執行select語句對查詢結果集封裝的過程。

ResultSetHandler

前面的文章中得知,mybatis會將結果集按照映射配置文件中定義的映射規則,如resultMap節點,映射成相應的結果對象。

在StatementHandler接口執行完指定的select語句後,會將查詢結果集交給ResultSetHandler完成映射處理。

ResultSetHandler接口代碼如下:

/**
 * 處理select查詢的結果集
 * @author Clinton Begin
 */
public interface ResultSetHandler {

    /**
     * 處理結果集,生成結果集集合
     * @param stmt
     * @param <E>
     * @return
     * @throws SQLException
     */
    <E> List<E> handleResultSets(Statement stmt) throws SQLException;

    /**
     * 處理結果集,返回相應的遊標
     * @param stmt
     * @param <E>
     * @return
     * @throws SQLException
     */
    <E> Cursor<E> handleCursorResultSets(Statement stmt) throws SQLException;

    /**
     * 處理存儲過程
     * @param cs
     * @throws SQLException
     */
    void handleOutputParameters(CallableStatement cs) throws SQLException;

}

ResultSetHandler只有DefaultResultSetHandler一個實現,該類是處理結果集映射的核心類。核心字段如下所示:

 

public class DefaultResultSetHandler implements ResultSetHandler {

    private static final Object DEFERRED = new Object();

    /**
     * MyBatis執行器
     */
    private final Executor executor;
    private final Configuration configuration;
    /**
     * Sql節點
     */
    private final MappedStatement mappedStatement;
    /**
     * 遊標
     */
    private final RowBounds rowBounds;
    /**
     * 參數處理器
     */
    private final ParameterHandler parameterHandler;
    /**
     * 結果集處理器
     */
    private final ResultHandler<?> resultHandler;
    /**
     * Sql對象
     */
    private final BoundSql boundSql;
    private final TypeHandlerRegistry typeHandlerRegistry;
    /**
     * 對象工廠和反射工廠
     */
    private final ObjectFactory objectFactory;
    private final ReflectorFactory reflectorFactory;

    /**
     * 映射緩存
     */
    private final Map<CacheKey, Object> nestedResultObjects = new HashMap<>();
    private final Map<String, Object> ancestorObjects = new HashMap<>();

    /**
     * 自動映射列緩存
     */
    private final Map<String, List<UnMappedColumnAutoMapping>> autoMappingsCache = new HashMap<>();

    /**
     * 記錄是否使用構造器創建映射對象
     */
    private boolean useConstructorMappings;
}

handlerResultSets方法

通過select語句查詢司機卡得到的結果集由handlerResultSets方法進行處理。該方法可以處理由Statement、PreparedStatement、CallableStatement產生的結果集。其中,Statement用於處理靜態SQL,PrepareStatement用於處理預處理的SQL,CallableStatement用於處理存儲過程,存儲過程的結果集可能有多個,mybatis中對多結果集也進行了處理。由於java開發多數是mysql,而mysql中存儲過程使用頻率非常之少,因此這裏不對多結果集進行講解。

handleResultSets方法的代碼如下。

 /**
     * ☆
     * select查詢到的結果集會在這裏被處理
     *
     * @param stmt
     * @return
     * @throws SQLException
     */
    @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);
        // 獲取到sql節點所有的resultMap(一般只有一個)
        List<ResultMap> resultMaps = mappedStatement.getResultMaps();
        // resultMap的數量
        int resultMapCount = resultMaps.size();
        validateResultMapsCount(rsw, resultMapCount);
        // 遍歷resultMaps
        while (rsw != null && resultMapCount > resultSetCount) {
            // 獲取指定下標的resultMap
            ResultMap resultMap = resultMaps.get(resultSetCount);
            // 處理resultSet
            handleResultSet(rsw, resultMap, multipleResults, null);
            // 獲取下一個結果集
            rsw = getNextResultSet(stmt);
            cleanUpAfterHandlingResultSet();
            resultSetCount++;
        }
        // resultSets是多結果集時適用。實際開發中幾乎不用這個
        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);
    }

查詢到的結果集可能有多個,mybatis默認先處理單結果集,getFirstResultSet方法用於獲取第一個結果集對象。而getNextResultSet則是用於獲取下一個結果集


    /**
     * 獲取第一個結果集對象。
     * 在操作存儲過程時,可能會得到多個結果集
     * 該方法只獲取第一個結果集
     *
     * @param stmt
     * @return
     * @throws SQLException
     */
    private ResultSetWrapper getFirstResultSet(Statement stmt) throws SQLException {
        // 獲取結果集
        ResultSet rs = stmt.getResultSet();
        // 結果集如果爲null就繼續向下獲取
        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;
    }

    /**
     * 獲取下一個resultSet結果集
     *
     * @param stmt
     * @return
     */
    private ResultSetWrapper getNextResultSet(Statement stmt) {
        // Making this method tolerant of bad JDBC drivers
        try {
            // 檢測jdbc是否支持多結果集
            if (stmt.getConnection().getMetaData().supportsMultipleResultSets()) {
                // 檢測是否還存在需要處理的結果集
                if (!(!stmt.getMoreResults() && stmt.getUpdateCount() == -1)) {
                    ResultSet rs = stmt.getResultSet();
                    if (rs == null) {
                        return getNextResultSet(stmt);
                    } else {
                        return new ResultSetWrapper(rs, configuration);
                    }
                }
            }
        } catch (Exception e) {
            // Intentionally ignored.
        }
        return null;
    }

在上面的代碼中,DefaultResultHandler在獲取到結果集對象之後,會將其封裝成ResultSetWrapper對象再進行處理。ResultSetWrapper對象中記錄了結果集的一些元數據,並提供了一系列操作ResultSet的輔助方法,下面是ResultSetWrapper的核心字段。


/**
 * 對ResultSet進行封裝
 * 存放了ResultSet的元數據
 *
 * @author Iwao AVE!
 */
public class ResultSetWrapper {

    /**
     * 查詢得到的結果集
     */
    private final ResultSet resultSet;
    /**
     * 一堆類型處理器
     */
    private final TypeHandlerRegistry typeHandlerRegistry;
    /**
     * resultSet每列列名
     */
    private final List<String> columnNames = new ArrayList<>();
    /**
     * 每列對應的java類型
     */
    private final List<String> classNames = new ArrayList<>();
    /**
     * 每列對應的jdbc類型
     */
    private final List<JdbcType> jdbcTypes = new ArrayList<>();
    /**
     * key是列名,value是TypeHandler
     */
    private final Map<String, Map<Class<?>, TypeHandler<?>>> typeHandlerMap = new HashMap<>();
    /**
     * 記錄被映射的列名
     */
    private final Map<String, List<String>> mappedColumnNamesMap = new HashMap<>();
    /**
     * 記錄未映射的列名
     */
    private final Map<String, List<String>> unMappedColumnNamesMap = new HashMap<>();
}

在ResultSetWrapper的構造方法中,會初始化columnNames、jdbcTypes、classNames三個集合,代碼如下。

    public ResultSetWrapper(ResultSet rs, Configuration configuration) throws SQLException {
        super();
        this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
        this.resultSet = rs;
        // 獲取resultSet的元信息
        final ResultSetMetaData metaData = rs.getMetaData();
        // 獲取resultSet列數
        final int columnCount = metaData.getColumnCount();
        // 遍歷每一列,封裝 列名、jdbc類型、java類型
        for (int i = 1; i <= columnCount; i++) {
            columnNames.add(configuration.isUseColumnLabel() ? metaData.getColumnLabel(i) : metaData.getColumnName(i));
            jdbcTypes.add(JdbcType.forCode(metaData.getColumnType(i)));
            classNames.add(metaData.getColumnClassName(i));
        }
    }

簡單映射

介紹完整體的流程,下面來看handleResultSet方法。該方法的核心功能是完成對單個結果集的映射(即單表查詢的映射)。代碼如下。

    /**
     * 根據resultMap定義的映射規則去處理resultSet。並將映射的結果添加到multipleResults集合
     *
     * @param rsw
     * @param resultMap
     * @param multipleResults
     * @param parentMapping
     * @throws SQLException
     */
    private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
        try {
            if (parentMapping != null) {
                // 處理結果集中的嵌套映射。(resultMap中套着resultMap)
                handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
            } else {
                if (resultHandler == null) {
                    // 用戶沒有指定resultHandler,就用DefaultResultHandler
                    DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
                    // 對resultSet進行映射,並將映射結果添加到defaultResultHandler
                    handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
                    // 將defaultResultHandler中保存的集合添加到multipleResults
                    multipleResults.add(defaultResultHandler.getResultList());
                } else {
                    // 使用用戶指定的resultHandler處理結果集
                    handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
                }
            }
        } finally {
            // issue #228 (close resultsets)
            closeResultSet(rsw.getResultSet());
        }
    }

該方法的核心代碼就是handleRowValues。方法中判斷是否包含嵌套映射去決定處理簡單映射還是嵌套映射,代碼如下。

    /**
     * 結果集映射核心方法
     *
     * @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);
        }
    }

下面先看簡單映射。簡單映射的核心代碼在handleRowValuesForSimpleResultMap方法中,下面先分析該方法執行流程。

  1. 調用skipRows方法,根據RowBounds中的offset值定位到指定的記錄行。RowBounds叫做遊標,後面的文章會對其進行介紹。
  2. 調用shouldProcessMoreRows方法檢測是否還有需要映射的記錄。
  3. 通過resolveDiscriminatedResultMap方法確定映射要使用的ResultMap對象
  4. 調用getRowValue方法對Result中的一行記錄進行映射。
    1. 通過createResultObject方法創建映射後的結果對象。
    2. 通過shouldApplyAutomaticMap平時方法檢測是否開啓了自動映射功能
    3. 通過applyAutomaiticMappings方法自動映射ResultMap中爲明確映射的列
    4. 通過applyPropertyMap平時方法映射ResultMap中明確映射的列,到這裏該行記錄的數據已經完全映射到了結果對象的相應屬性中。
  5. 調用storeObject方法保存映射得到的結果集對象

handleRowValuesForSimpleResultMap代碼如下。

    /**
     * 簡單結果集映射處理
     *
     * @param rsw
     * @param resultMap
     * @param resultHandler
     * @param rowBounds
     * @param parentMapping
     * @throws SQLException
     */
    private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
            throws SQLException {
        DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
        ResultSet resultSet = rsw.getResultSet();
        // 通過遊標的offset值定位到指定的記錄行
        skipRows(resultSet, rowBounds);
        // 檢測是否還有需要映射的記錄
        while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
            // 確定使用的ResultMap對象。多數情況下,這裏指的還是傳入的ResultMap
            ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
            // 映射
            Object rowValue = getRowValue(rsw, discriminatedResultMap, null);
            // 保存映射結果
            storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
        }
    }

skipRows方法是根據RowBounds.offset字段的值定位到指定的記錄。

    /**
     * 通過遊標定位到指定行
     *
     * @param rs
     * @param rowBounds
     * @throws SQLException
     */
    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;
                }
            }
        }
    }

定位到指定的記錄行之後,再通過shouldProcessMoreRows方法檢測是否還有需要映射的行。

    /**
     * 檢測是否還有需要映射的數據
     *
     * @param context
     * @param rowBounds
     * @return
     */
    private boolean shouldProcessMoreRows(ResultContext<?> context, RowBounds rowBounds) {
        return !context.isStopped() && context.getResultCount() < rowBounds.getLimit();
    }

resolveDiscriminatedResultMap方法會根據ResultMap對象中記錄的Discriminator以及參與映射的列值,選擇映射操作最終使用的ResultMap,具體實現如下,。

    /**
     * 根據ResultMap中記錄的Discriminator對象以及參與映射的記錄行中的列值
     * 確定使用的ResultMap對象
     *
     * @param rs
     * @param resultMap
     * @param columnPrefix
     * @return
     * @throws SQLException
     */
    public ResultMap resolveDiscriminatedResultMap(ResultSet rs, ResultMap resultMap, String columnPrefix) throws SQLException {
        Set<String> pastDiscriminators = new HashSet<>();
        // 通過discriminator標籤去確定使用哪個ResultMap。使用不多,不進行註釋
        Discriminator discriminator = resultMap.getDiscriminator();
        while (discriminator != null) {
            final Object value = getDiscriminatorValue(rs, discriminator, columnPrefix);
            final String discriminatedMapId = discriminator.getMapIdFor(String.valueOf(value));
            if (configuration.hasResultMap(discriminatedMapId)) {
                resultMap = configuration.getResultMap(discriminatedMapId);
                Discriminator lastDiscriminator = discriminator;
                discriminator = resultMap.getDiscriminator();
                if (discriminator == lastDiscriminator || !pastDiscriminators.add(discriminatedMapId)) {
                    break;
                }
            } else {
                break;
            }
        }
        return resultMap;
    }
    private Object getDiscriminatorValue(ResultSet rs, Discriminator discriminator, String columnPrefix) throws SQLException {
        final ResultMapping resultMapping = discriminator.getResultMapping();
        final TypeHandler<?> typeHandler = resultMapping.getTypeHandler();
        return typeHandler.getResult(rs, prependPrefix(resultMapping.getColumn(), columnPrefix));
    }

通過上面方法的處理確定了映射使用的ResultMap對象,之後會調用getRowValue完成對該記錄的映射。首先根據ResultMap指定的類型創建對應的結果對象和MetaObject,再根據配置信息決定是否自動映射ResultMap中未明確映射的列,映射完畢後返回結果對象。代碼如下。

    /**
     * 映射
     *
     * @param rsw
     * @param resultMap
     * @param columnPrefix
     * @return
     * @throws SQLException
     */
    private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
        // 與延遲加載有關
        final ResultLoaderMap lazyLoader = new ResultLoaderMap();
        // 創建該行記錄映射之後的結果對象,就是resultMap的type屬性指定的類
        Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
        if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
            // 創建上面對象的metaObject
            final MetaObject metaObject = configuration.newMetaObject(rowValue);
            // 成功映射任意屬性,則爲true,否則爲false
            boolean foundValues = this.useConstructorMappings;
            // 檢測是否需要自動映射
            if (shouldApplyAutomaticMappings(resultMap, false)) {
                // 自動映射resultMap未指定的列
                foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
            }
            // 映射resultMap指定的列
            foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
            foundValues = lazyLoader.size() > 0 || foundValues;
            // 如果沒有映射任何屬性,就根據mybatis-config.xml配置的returnInstanceForEmptyRow配置決定如何返回
            rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
        }
        return rowValue;
    }

該方法中,createResultObject方法負責創建數據庫記錄映射得到的結果對象,該方法會返回結果集的列數、constructorResultMappings集合等信息,選擇不同的方式創建結果對象。具體實現如下。

    /**
     * 創建該行記錄映射之後的結果對象,就是resultMap的type屬性指定的類
     *
     * @param rsw
     * @param resultMap
     * @param lazyLoader
     * @param columnPrefix
     * @return
     * @throws SQLException
     */
    private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
        this.useConstructorMappings = false; // reset previous mapping result
        // 構造的參數類型
        final List<Class<?>> constructorArgTypes = new ArrayList<>();
        // 構造參數
        final List<Object> constructorArgs = new ArrayList<>();
        // 創建該行記錄的結果對象。該方法是該步驟的核心
        Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix);
        // TODO 如果包含嵌套查詢且配置了延遲加載,就創建代理對象
        if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
            final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
            for (ResultMapping propertyMapping : propertyMappings) {
                // issue gcode #109 && issue #149
                if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) {
                    resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
                    break;
                }
            }
        }
        // 記錄是否使用構造器創建對象
        this.useConstructorMappings = resultObject != null && !constructorArgTypes.isEmpty(); // set current mapping result
        return resultObject;
    }

    /**
     * 創建映射結果對象
     *
     * @param rsw
     * @param resultMap
     * @param constructorArgTypes
     * @param constructorArgs
     * @param columnPrefix
     * @return
     * @throws SQLException
     */
    private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, List<Class<?>> constructorArgTypes, List<Object> constructorArgs, String columnPrefix)
            throws SQLException {
        // 根據resultMap配置的type屬性去創建對應的MetaClass
        final Class<?> resultType = resultMap.getType();
        final MetaClass metaType = MetaClass.forClass(resultType, reflectorFactory);
        // 獲取到constructor節點
        final List<ResultMapping> constructorMappings = resultMap.getConstructorResultMappings();
        if (hasTypeHandlerForResultObject(rsw, resultType)) {
            // 結果集只有一列,並且存在TypeHandler對象可以將該列轉換成resultType指定的值(Integer、String等)
            return createPrimitiveResultObject(rsw, resultMap, columnPrefix);
        } else if (!constructorMappings.isEmpty()) {
            // resultMap中指定了constructor標籤,通過反射方式調用構造方法創建對象
            return createParameterizedResultObject(rsw, resultType, constructorMappings, constructorArgTypes, constructorArgs, columnPrefix);
        } else if (resultType.isInterface() || metaType.hasDefaultConstructor()) {
            // 使用默認的無參構造創建
            return objectFactory.create(resultType);
        } else if (shouldApplyAutomaticMappings(resultMap, false)) {
            // 通過自動映射,查找合適的構造方法創建
            return createByConstructorSignature(rsw, resultType, constructorArgTypes, constructorArgs);
        }
        throw new ExecutorException("Do not know how to create an instance of " + resultType);
    }

完成了對映射結果對象的創建後,下面就會將一行記錄的各個列映射到該結果集對象的對應屬性中。在成功創建對象並且獲取到MetaObject之後,會調用shouldApplyAutomaticMappings方法檢測是否允許自動映射,如果允許則調用applyAutomaiticMappings方法對ResultMap未指定的列進行自動映射。

    /**
     * 是否需要自動映射。
     *
     * @param resultMap
     * @param isNested
     * @return
     */
    private boolean shouldApplyAutomaticMappings(ResultMap resultMap, boolean isNested) {
        if (resultMap.getAutoMapping() != null) {
            return resultMap.getAutoMapping();
        } else {
            if (isNested) {
                return AutoMappingBehavior.FULL == configuration.getAutoMappingBehavior();
            } else {
                return AutoMappingBehavior.NONE != configuration.getAutoMappingBehavior();
            }
        }
    }

    /**
     * 自動映射未指定的列
     * @param rsw
     * @param resultMap
     * @param metaObject
     * @param columnPrefix
     * @return
     * @throws SQLException
     */
    private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
        // 查找需要自動映射的列
        List<UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);
        boolean foundValues = false;
        if (!autoMapping.isEmpty()) {
            // 映射列不爲空,一一映射
            for (UnMappedColumnAutoMapping mapping : autoMapping) {
                // 從resultSet獲取值
                final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);
                if (value != null) {
                    foundValues = true;
                }
                if (value != null || (configuration.isCallSettersOnNulls() && !mapping.primitive)) {
                    // gcode issue #377, call setter on nulls (value is not 'found')
                    metaObject.setValue(mapping.property, value);
                }
            }
        }
        return foundValues;
    }

createAutomaticMappings方法負責爲未映射的列查找對應的屬性,並將二者關聯起來封裝成UnMappedColumnAutoMapping對象。createAutomaticMappings方法的具體實現如下。

    /**
     * 查找需要自動映射的列
     * @param rsw
     * @param resultMap
     * @param metaObject
     * @param columnPrefix
     * @return
     * @throws SQLException
     */
    private List<UnMappedColumnAutoMapping> createAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
        final String mapKey = resultMap.getId() + ":" + columnPrefix;
        // 先從緩存中找
        List<UnMappedColumnAutoMapping> autoMapping = autoMappingsCache.get(mapKey);
        if (autoMapping == null) {
            autoMapping = new ArrayList<>();
            // 獲取未映射的列
            final List<String> unmappedColumnNames = rsw.getUnmappedColumnNames(resultMap, columnPrefix);
            for (String columnName : unmappedColumnNames) {
                // 默認列名就是屬性名
                String propertyName = columnName;
                // 列前綴不爲空時處理。
                if (columnPrefix != null && !columnPrefix.isEmpty()) {
                    // When columnPrefix is specified,
                    // ignore columns without the prefix.
                    if (columnName.toUpperCase(Locale.ENGLISH).startsWith(columnPrefix)) {
                        propertyName = columnName.substring(columnPrefix.length());
                    } else {
                        continue;
                    }
                }
                // 根據列名查找對應的屬性
                final String property = metaObject.findProperty(propertyName, configuration.isMapUnderscoreToCamelCase());
                // 屬性不爲空並且有set方法
                if (property != null && metaObject.hasSetter(property)) {
                    // 該列已經映射,不重複映射
                    if (resultMap.getMappedProperties().contains(property)) {
                        continue;
                    }
                    final Class<?> propertyType = metaObject.getSetterType(property);
                    if (typeHandlerRegistry.hasTypeHandler(propertyType, rsw.getJdbcType(columnName))) {
                        // 查找對應的TypeHandler對象
                        final TypeHandler<?> typeHandler = rsw.getTypeHandler(propertyType, columnName);
                        // 將該列添加到autoMapping集合中
                        autoMapping.add(new UnMappedColumnAutoMapping(columnName, property, typeHandler, propertyType.isPrimitive()));
                    } else {
                        configuration.getAutoMappingUnknownColumnBehavior()
                                .doAction(mappedStatement, columnName, property, propertyType);
                    }
                } else {
                    configuration.getAutoMappingUnknownColumnBehavior()
                            .doAction(mappedStatement, columnName, (property != null) ? property : propertyName, null);
                }
            }
            // 存放到緩存
            autoMappingsCache.put(mapKey, autoMapping);
        }
        return autoMapping;
    }

通過applyAutomaiticMappings方法處理完自動映射之後,後續會通過applyPropertyMappings方法對ResultMap中指定的列進行映射,核心代碼如下。

    /**
     * 根據配置去映射
     * @param rsw
     * @param resultMap
     * @param metaObject
     * @param lazyLoader
     * @param columnPrefix
     * @return
     * @throws SQLException
     */
    private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, ResultLoaderMap lazyLoader, String columnPrefix)
            throws SQLException {
        // 獲取需要映射的列名
        final List<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
        boolean foundValues = false;
        // 獲取所有resultMapping
        final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
        for (ResultMapping propertyMapping : propertyMappings) {
            // 獲取列名
            String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
            if (propertyMapping.getNestedResultMapId() != null) {
                // 判斷該節點是否是對其他ResultMapping進行引用
                column = null;
            }
            // 場景1:column是{prop1=col1,prop2=col2}形式
            if (propertyMapping.isCompositeResult()
                    // 場景2:基本類型的屬性映射
                    || (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH)))
                    // 場景3:多結果集處理
                    || propertyMapping.getResultSet() != null) {
                // 完成映射,得到屬性值
                Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix);
                // 獲取屬性名稱
                final String property = propertyMapping.getProperty();
                if (property == null) {
                    continue;
                } else if (value == DEFERRED) {
                    // DEFERRED指佔位符對象
                    foundValues = true;
                    continue;
                }
                if (value != null) {
                    foundValues = true;
                }
                if (value != null || (configuration.isCallSettersOnNulls() && !metaObject.getSetterType(property).isPrimitive())) {
                    // gcode issue #377, call setter on nulls (value is not 'found')
                    // 設置屬性值
                    metaObject.setValue(property, value);
                }
            }
        }
        return foundValues;
    }

其中,映射操作是在getPropertyMappingValue方法中完成,具體代碼如下,

    /**
     * 完成映射操作並獲取屬性值
     * @param rs
     * @param metaResultObject
     * @param propertyMapping
     * @param lazyLoader
     * @param columnPrefix
     * @return
     * @throws SQLException
     */
    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);
            return DEFERRED;
        } else {
            final TypeHandler<?> typeHandler = propertyMapping.getTypeHandler();
            final String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
            // 使用typeHandler來獲取屬性值
            return typeHandler.getResult(rs, column);
        }
    }

到這裏,已經得到了一個完整映射的結果對象,之後DefaultResultSetHandler會通過storeObject方法將該結果對象保存到合適的位置,這樣該行記錄就完成了。如果是嵌套映射或者嵌套查詢的結果對象則保存到父對象對應的屬性中,如果是簡單映射則保存到ResultHandler中。

    /**
     * 保存映射結果
     * @param resultHandler
     * @param resultContext
     * @param rowValue
     * @param parentMapping
     * @param rs
     * @throws SQLException
     */
    private void storeObject(ResultHandler<?> resultHandler, DefaultResultContext<Object> resultContext, Object rowValue, ResultMapping parentMapping, ResultSet rs) throws SQLException {
        if (parentMapping != null) {
            // 嵌套映射,保存在父對象屬性中
            linkToParents(rs, parentMapping, rowValue);
        } else {
            // 普通映射,保存在ResultHandler
            callResultHandler(resultHandler, resultContext, rowValue);
        }
    }

    private void callResultHandler(ResultHandler<?> resultHandler, DefaultResultContext<Object> resultContext, Object rowValue) {
        resultContext.nextResultObject(rowValue);
        ((ResultHandler<Object>) resultHandler).handleResult(resultContext);
    }

至此,簡單映射的流程就介紹完了。

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