*************************************優雅的分割線 **********************************
分享一波:程序員賺外快-必看的巔峰干貨
如果以上內容對你覺得有用,并想獲取更多的賺錢方式和免費的技術教程
請關注微信公眾號:HB荷包
一個能讓你學習技術和賺錢方法的公眾號,持續更新
*************************************優雅的分割線 **********************************
前言
這段時間疫情原因躺在家做咸魚,代碼也沒怎么敲,源碼也沒怎么看,博客拖更了一個月,今天心血來潮繼續讀了點源碼,晚上正好抽空發個博客,證明我還活著。
關于結果集映射,在一個月前的博客中已經將簡單映射給講述完畢,在實際應用中,除了單表查詢以外,還可能通過連表查詢多張表的記錄,這些記錄需要映射成多個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
該方法是處理嵌套映射的核心方法,有以下主要步驟
通過skipRows方法定位到指定的記錄行
通過shouldProcessMoreRows方法檢測是否能夠繼續映射結果集中剩余的記錄行
調用resolveDiscriminatedResultMap方法,根據ResultMap中記錄的Discriminator對象以及參與映射的記錄行中相應的列值,決定映射使用的ResultMap對象。
通過createRowKey方法為該行記錄生成CacheKey,CacheKey作為緩存中的key值,同時在嵌套映射中也作為key唯一標識一個結果集對象。
根據上面步驟生成的CacheKey查詢DefaultRe.nestedResultObjects集合,這個字段是一個HashMap,在處理嵌套映射過程中生成的所有結果對象,都會生成相應的CacheKey并保存到該集合。
檢測<select>節點中resultOrdered屬性的配置,該設置僅對嵌套映射有效。當Ordered屬性為true時,則認為返回一個主結果行
通過getRowValue,完成當前記錄行的映射操作并返回結果對象,其中還會講結果對象添加到nestedResultObjects集合中。
通過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()) {// 得到本次查詢使用的ResultMapfinal ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);// 為該行記錄生成CacheKey,作為緩存中的key值final CacheKey rowKey = createRowKey(discriminatedResultMap, rsw, null);// 根據緩存key先獲取映射緩存Object partialObject = nestedResultObjects.get(rowKey);// 檢測select節點中的resultOrder屬性。該屬性只針對嵌套映射有效。// 當true時則認為返回一個主結果行時,不會記錄nestedResultObjectif (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) {// 將生成結果保存到ResultHandlerstoreObject(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的過程如下。
嘗試使用<idArg>節點或者<id>節點中定義的列名以及該列在當前記錄行中對應的列值生成CacheKey
如果ResultMap中沒有定義這兩個節點,則有ResultMap中明確要映射的列名以及它們在當前記錄行中對應的列值一起構成CacheKey對象
經過上面兩個步驟后如果依然查不到相關的列名和列值,且ResultMap的type屬性明確指明了結果對象為Map類型,則有結果集中所有列名以及改行記錄行的所有列值一起構成CacheKey
如果映射的結果對象不是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())) {// 由結果集中的所有列名以及當前記錄行的所有列值一起構成CacheKeycreateRowKeyForMap(rsw, cacheKey);} else {// 由結果集中未映射的列名以及它們在當前記錄行中的對應列值一起構成CacheKey對象createRowKeyForUnmappedProperties(resultMap, rsw, cacheKey, columnPrefix);}} else {// 由ResultMapping集合中的列名以及它們在當前記錄行中相應的列值一起構成CacheKeycreateRowKeyForMappedProperties(resultMap, rsw, cacheKey, resultMappings, columnPrefix);}// 如果在上面的過程沒有找到任何列參與構成CacheKey對象,則返回NullCacheKeyif (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);// 獲取該列相應的TypeHandlerfinal 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方法,完成對記錄行的映射,步驟如下。
檢測外層對象是否已經存在
如果外層對象不存在
調用createRowObject方法創建外層對象
將外層對象添加到DefaultResultSetHandler.ancestorObject集合中,其中key是ResultMap的id,value為外層對象。
通過通過applyNestedResultMappings方法處理嵌套映射,其中會將生成的結果對象設置到外層對象的相應的屬性中。
將外層的ancestorObject集合中移除
將外層對象保存到nestedResultObjects集合中。
如果外層對象已存在
將外層對象添加到ancestorObjects集合中
通過applyNestedResultMappings方法處理嵌套映射,其中會將生成的結果對象設置到外層對象的相應屬性中
將外層對象從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);// 將外層對象添加到ancestorObjectsputAncestor(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) {// 將外層對象添加到nestedResultObjectsnestedResultObjects.put(combinedKey, rowValue);}}return rowValue;
}
applyNestedResultMappings方法
處理嵌套邏輯的核心在這個方法中,該方法會遍歷ResultMap.propertyResultMappings集合中記錄的ResultMapping對象,并處理其中的嵌套映射。該方法步驟如下。
獲取ResultMapping.nestedResultMapId字段值,該值不為空則表示存在相應的嵌套映射要處理。同時還會檢測ResultMapping.resultSet字段,它指定了要映射的結果及名稱,該屬性的映射在前面的handleResultSets方法中完成。
通過resolveDiscriminatedResultMap方法確定嵌套映射使用的ResultMap對象
處理循環引用的場景,如果不存在循環引用的情況,則繼續后面的映射流程。如果存在循環引用,則不在創建新的對象,而是重用前面的對象
通過createRowKey方法為嵌套對象創建CacheKey。該過程除了根據嵌套對象的信息創建CacheKey,還會與外層對象的CacheKey合并,得到全局唯一的CacheKey
如果外層對象中用于記錄當前嵌套對象的屬性為Collection并且未初始化,則會通過instantiateCollectionPropertyIfAppropriate方法初始化該對象
根據association、collection等節點的notNullColumn屬性,檢測結果集中相應的列是否為空
調用getRowValue方法完成嵌套映射,并生成嵌套對象。嵌套對象可以嵌套多層,也就可以產生多層遞歸。
通過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的idfinal String nestedResultMapId = resultMapping.getNestedResultMapId();// 如果指定了嵌套映射的id,并且尚未映射if (nestedResultMapId != null && resultMapping.getResultSet() == null) {try {// 獲取列前綴final String columnPrefix = getColumnPrefix(parentPrefix, resultMapping);// 根據上面獲取到的嵌套映射id去從配置中找到對應的ResultMapfinal 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);// 合并CacheKeyfinal 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;
}
結語
距離上一篇源碼分析的博客已經間隔了一個多月,最近在家閑夠了就著手繼續寫博客了,關于這塊的內容不會棄坑,只是偶爾會拖更一下下。。
*************************************優雅的分割線 **********************************
分享一波:程序員賺外快-必看的巔峰干貨
如果以上內容對你覺得有用,并想獲取更多的賺錢方式和免費的技術教程
請關注微信公眾號:HB荷包
一個能讓你學習技術和賺錢方法的公眾號,持續更新
*************************************優雅的分割線 **********************************