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

*************************************優雅的分割線 **********************************

分享一波:程序員賺外快-必看的巔峰干貨

如果以上內容對你覺得有用,并想獲取更多的賺錢方式和免費的技術教程

請關注微信公眾號:HB荷包
在這里插入圖片描述
一個能讓你學習技術和賺錢方法的公眾號,持續更新
*************************************優雅的分割線 **********************************
前言

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

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

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

ResultSetHandler接口代碼如下:

/**

  • 處理select查詢的結果集

  • @author Clinton Begin
    */
    public interface ResultSetHandler {

    /**

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

    /**

    • 處理結果集,返回相應的游標
    • @param stmt
    • @param
    • @return
    • @throws SQLException
      */
      Cursor 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 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);// 遍歷resultMapswhile (rsw != null && resultMapCount > resultSetCount) {// 獲取指定下標的resultMapResultMap resultMap = resultMaps.get(resultSetCount);// 處理resultSethandleResultSet(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 resultsetbreak;}}}return rs != null ? new ResultSetWrapper(rs, configuration) : null;
}/*** 獲取下一個resultSet結果集** @param stmt* @return*/
private ResultSetWrapper getNextResultSet(Statement stmt) {// Making this method tolerant of bad JDBC driverstry {// 檢測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 columnNames = new ArrayList<>();
      /
      *
    • 每列對應的java類型
      /
      private final List classNames = new ArrayList<>();
      /
      *
    • 每列對應的jdbc類型
      /
      private final List jdbcTypes = new ArrayList<>();
      /
      *
    • key是列名,value是TypeHandler
      /
      private final Map<String, Map<Class<?>, TypeHandler<?>>> typeHandlerMap = new HashMap<>();
      /
      *
    • 記錄被映射的列名
      /
      private final Map<String, List> mappedColumnNamesMap = new HashMap<>();
      /
      *
    • 記錄未映射的列名
      */
      private final Map<String, List> 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,就用DefaultResultHandlerDefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);// 對resultSet進行映射,并將映射結果添加到defaultResultHandlerhandleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);// 將defaultResultHandler中保存的集合添加到multipleResultsmultipleResults.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方法中,下面先分析該方法執行流程。

調用skipRows方法,根據RowBounds中的offset值定位到指定的記錄行。RowBounds叫做游標,后面的文章會對其進行介紹。
調用shouldProcessMoreRows方法檢測是否還有需要映射的記錄。
通過resolveDiscriminatedResultMap方法確定映射要使用的ResultMap對象
調用getRowValue方法對Result中的一行記錄進行映射。通過createResultObject方法創建映射后的結果對象。通過shouldApplyAutomaticMap平時方法檢測是否開啟了自動映射功能通過applyAutomaiticMappings方法自動映射ResultMap中為明確映射的列通過applyPropertyMap平時方法映射ResultMap中明確映射的列,到這里該行記錄的數據已經完全映射到了結果對象的相應屬性中。
調用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對象。多數情況下,這里指的還是傳入的ResultMapResultMap 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())) {// 創建上面對象的metaObjectfinal MetaObject metaObject = configuration.newMetaObject(rowValue);// 成功映射任意屬性,則為true,否則為falseboolean 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 #149if (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 resultreturn 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屬性去創建對應的MetaClassfinal 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;// 獲取所有resultMappingfinal 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 {// 普通映射,保存在ResultHandlercallResultHandler(resultHandler, resultContext, rowValue);}
}private void callResultHandler(ResultHandler<?> resultHandler, DefaultResultContext<Object> resultContext, Object rowValue) {resultContext.nextResultObject(rowValue);((ResultHandler<Object>) resultHandler).handleResult(resultContext);
}

至此,簡單映射的流程就介紹完了。
*************************************優雅的分割線 **********************************

分享一波:程序員賺外快-必看的巔峰干貨

如果以上內容對你覺得有用,并想獲取更多的賺錢方式和免費的技術教程

請關注微信公眾號:HB荷包
在這里插入圖片描述
一個能讓你學習技術和賺錢方法的公眾號,持續更新
*************************************優雅的分割線 **********************************

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:
http://www.pswp.cn/news/536975.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/536975.shtml
英文地址,請注明出處:http://en.pswp.cn/news/536975.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

kdj買賣指標公式源碼_通達信指標公式源碼MACD背離KDJ背離指標

N1:5;N2:10;N3:21;N4:60;牛熊:EMA(CLOSE,N4),COLORGREEN,LINETHICK3;DIFF:EMA(CLOSE,12) - EMA(CLOSE,26);DEA:EMA(DIFF,8);A1:BARSLAST(REF(CROSS(DIFF,DEA),1)); B1:REF(C,A11)>C AND REF(DIFF,A11)DRAWTEXT(IF(B1>0,1,0),L-0.1,MACD底背),COLORGREEN;RSV:(CLOSE-LLV(L…

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

*************************************優雅的分割線 ********************************** 分享一波:程序員賺外快-必看的巔峰干貨 如果以上內容對你覺得有用,并想獲取更多的賺錢方式和免費的技術教程 請關注微信公眾號:HB荷包 一個能讓你學習技術和賺錢方法的公眾號,持續更…

18.requests

多多的的轉載于:https://www.cnblogs.com/yangyangchunchun/p/10368337.html

gridview獲取選中行數據_Word轉Excel,不想熬夜加班,那就掌握這個數據清洗方法...

私信回復關鍵詞【福利】~獲取豐富辦公資源&#xff0c;助你高效辦公早下班&#xff01;小伙伴們&#xff0c;大家好&#xff0c;我是專治各種疑難雜「數」的農夫~今天&#xff0c;我就為大家介紹一種高效的數據清洗方法&#xff0c;助你告別熬夜加班&#xff0c;擁抱美好的夜晚…

如何深入學習python_菜鳥如何學好python

python在我國發展得如火如荼&#xff0c;因其操作簡單&#xff0c;應用廣泛受到很多人的喜歡。下面小編就來說說菜鳥如何學好python&#xff0c;一起來看看吧!1. 了解編程的基礎知識種是變量、編程規范、基本語法等&#xff0c;這也是開始編寫Python代碼的先決條件。第二種是數…

HTML5中本地儲存概念是什么,什么優點 ,與cookie有什么區別?

html5中的Web Storage 包括了兩種存儲方式&#xff1a; sessionStorage 和 localStorage. seessionStorage 用于本地存儲一個會話&#xff08;session&#xff09;中的數據&#xff0c;這些數據只有在同一個會話中的頁面才能訪問并且當會話結束后數據也隨之銷毀。因此session…

Mybatis源碼閱讀(三):結果集映射3.3 —— 主鍵生成策略

*************************************優雅的分割線 ********************************** 分享一波:程序員賺外快-必看的巔峰干貨 如果以上內容對你覺得有用,并想獲取更多的賺錢方式和免費的技術教程 請關注微信公眾號:HB荷包 一個能讓你學習技術和賺錢方法的公眾號,持續更…

list最大容量_Java 基礎(四)集合源碼解析 List

List 接口前面我們學習了Iterator、Collection&#xff0c;為集合的學習打下了基礎&#xff0c;現在我們來學習集合的第一大體系 List。List 是一個接口&#xff0c;定義了一組元素是有序的、可重復的集合。List 繼承自 Collection&#xff0c;較之 Collection&#xff0c;List…

Mybatis源碼閱讀(四):核心接口4.1——StatementHandler

*************************************優雅的分割線 ********************************** 分享一波:程序員賺外快-必看的巔峰干貨 如果以上內容對你覺得有用,并想獲取更多的賺錢方式和免費的技術教程 請關注微信公眾號:HB荷包 一個能讓你學習技術和賺錢方法的公眾號,持續更…

Shell學習之結合正則表達式與通配符的使用(五)

Shell學習之結合正則表達式與通配符的使用 目錄 通配符 正則表達式與通配符通配符通配符的使用正則表達式 正則表達式正則表達式的使用通配符 正則表達式與通配符 正則表達式用來在文件中匹配符合條件的字符串&#xff0c;正則是包含匹配。grep、awk、sed等命令可以支持正則表達…

Mybatis源碼閱讀(四):核心接口4.2——Executor(上)

*************************************優雅的分割線 ********************************** 分享一波:程序員賺外快-必看的巔峰干貨 如果以上內容對你覺得有用,并想獲取更多的賺錢方式和免費的技術教程 請關注微信公眾號:HB荷包 一個能讓你學習技術和賺錢方法的公眾號,持續更…

接收xml參數_SpringBoot實戰(二):接收xml請求

強烈推薦一個大神的人工智能的教程&#xff1a;http://www.captainbed.net/zhanghan【前言】最近在對接一個第三方系統&#xff0c;需要接收第三方系統的回調&#xff0c;而且格式為XML形式&#xff0c;之前自己一般接收的參數是Json形式&#xff0c;于是乎做個實驗驗證一下使用…

報錯 插入更新_window如何解決mysql數據量過大導致的報錯

window如何解決報錯“The total number of locks exceeds the lock table size”第一大步&#xff0c;查看mysql配置信息在CMD中輸入mysql -hlocalhost -uroot -p #如果設置了密碼直接接在p 后面 show variables like %storage_engine%以下為結果可以看到InnoDB是MySQL的默認引…

148. Sort List

Sort a linked list in O(n log n) time using constant space complexity. Example 1: Input: 4->2->1->3 Output: 1->2->3->4 Example 2: Input: -1->5->3->4->0 Output: -1->0->3->4->5難度&#xff1a;medium 題目&#xff1a;排…

Mybatis源碼閱讀(四):核心接口4.2——Executor(下)

*************************************優雅的分割線 ********************************** 分享一波:程序員賺外快-必看的巔峰干貨 如果以上內容對你覺得有用,并想獲取更多的賺錢方式和免費的技術教程 請關注微信公眾號:HB荷包 一個能讓你學習技術和賺錢方法的公眾號,持續更…

python解橢圓方程的例題_橢圓標準方程典型例題及練習題

橢圓標準方程典型例題例1已知P 點在以坐標軸為對稱軸的橢圓上&#xff0c;點P 到兩焦點的距離分別為354和352&#xff0c;過P 點作焦點所在軸的垂線&#xff0c;它恰好過橢圓的一個焦點&#xff0c;求橢圓方程&#xff0e; 解&#xff1a;設兩焦點為1F 、2F &#xff0c;且3541…

leetcode393. UTF-8 Validation

題目要求 A character in UTF8 can be from 1 to 4 bytes long, subjected to the following rules:For 1-byte character, the first bit is a 0, followed by its unicode code. For n-bytes character, the first n-bits are all ones, the n1 bit is 0, followed by n-1 by…

Mybatis源碼閱讀(五 ):接口層——SqlSession

*************************************優雅的分割線 ********************************** 分享一波:程序員賺外快-必看的巔峰干貨 如果以上內容對你覺得有用,并想獲取更多的賺錢方式和免費的技術教程 請關注微信公眾號:HB荷包 一個能讓你學習技術和賺錢方法的公眾號,持續更…

插入公式_一個小工具,徹底幫你搞定在Markdown中插入公式的問題

在編輯Markdown文檔時&#xff0c;插入公式是一個挺麻煩的活兒。需要掌握LaTex語法。我自己看完語法后&#xff0c;直接放棄&#xff0c;這絕對是反人類的語法。&#xff08;好吧&#xff0c;是我不會用...&#xff09;但是&#xff0c;我相信你看了這篇文章后&#xff0c;絕對…

JavaScript數據結構與算法——字典

1.字典數據結構 在字典中&#xff0c;存儲的是【鍵&#xff0c;值】對&#xff0c;其中鍵名是用來查詢特定元素的。字典和集合很相似&#xff0c;集合以【值&#xff0c;值】的形式存儲&#xff0c;字典則是用【鍵&#xff0c;值】對的形式存儲。字典也稱作映射。 2.創建字典 f…