mybatis原始碼-原來resultMap解析完是這樣
在 select 語句中查詢得到的是一張二維表, 水平方向上看是一個個欄位, 垂直方向上看是一條條記錄。
作為面向物件的語言, Java 中的的物件是根據類定義建立的。 類之間的引用關係可以認為是巢狀的關係。
在 mybatis 中, resultMap 節點定義了結果集和結果物件( JavaBean )之間的對映規則。
本文主要講解的是 resultMap 的解析。
1 兩個基礎類
在閱讀本文之前, 最好能對這兩個類有相應的理解。
1.1 列對映類ResultMapping
ResultMapping 物件記錄了結果集中一列與隊友JavaBean中一個屬性的對應關係。
更多詳情, 請參考 ofollow,noindex" target="_blank">mybatis百科-列對映類ResultMapping
1.2 結果集對映類ResultMap
ResultMap
對應的是結果集 <resultMap>中的一個結果集。 其基本組成部分中, 含有 ResultMapping
物件。
其組成大致如下:

更多詳情, 請參考 mybatis百科-結果集對映類ResultMap
2. 解析
2.1 入口函式
resultMap 是 mapper.xml 檔案下的, 因此其是解析 Mapper 的一個環節。
resultMapElements(context.evalNodes("/mapper/resultMap"));
解析< resultMap >, 由於< resultMap >是可以有多個的, 因此, context.evalNodes("/mapper/resultMap")
返回的是一個 List
。
private void resultMapElements(List<XNode> list) throws Exception { // 遍歷, 解析 for (XNode resultMapNode : list) { try { resultMapElement(resultMapNode); } catch (IncompleteElementException e) { // ignore, it will be retried } } }
2.2
整個過程就是 resultMapElement 這個函式。其流程大體如下
對應的程式碼
private ResultMap resultMapElement(XNode resultMapNode) throws Exception { return resultMapElement(resultMapNode, Collections.<ResultMapping> emptyList()); } /** * 處理 <resultMap> 節點, 將節點解析成 ResultMap 物件, 下面包含有 ResultMapping 物件組成的列表 * @param resultMapNode resultMap 節點 * @param additionalResultMappings 另外的 ResultMapping 列 * @return ResultMap 物件 * @throws Exception */ private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception { ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier()); // 獲取 ID , 預設值會拼裝所有父節點的 id 或 value 或 property String id = resultMapNode.getStringAttribute("id", resultMapNode.getValueBasedIdentifier()); // 獲取 type 屬性, 表示結果集將被對映為 type 指定型別的物件 String type = resultMapNode.getStringAttribute("type", resultMapNode.getStringAttribute("ofType", resultMapNode.getStringAttribute("resultType", resultMapNode.getStringAttribute("javaType")))); // 獲取 extends 屬性, 其表示結果集的繼承 String extend = resultMapNode.getStringAttribute("extends"); // 自動對映屬性。 將列名自動對映為屬性 Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping"); // 解析 type, 獲取其型別 Class<?> typeClass = resolveClass(type); Discriminator discriminator = null; // 記錄解析的結果 List<ResultMapping> resultMappings = new ArrayList<>(); resultMappings.addAll(additionalResultMappings); // 處理子節點 List<XNode> resultChildren = resultMapNode.getChildren(); for (XNode resultChild : resultChildren) { // 處理 constructor 節點 if ("constructor".equals(resultChild.getName())) { // 解析建構函式元素,其下的沒每一個子節點都會生產一個 ResultMapping 物件 processConstructorElement(resultChild, typeClass, resultMappings); // 處理 discriminator 節點 } else if ("discriminator".equals(resultChild.getName())) { discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings); // 處理其餘節點, 如 id, result, assosation d等 } else { List<ResultFlag> flags = new ArrayList<>(); if ("id".equals(resultChild.getName())) { flags.add(ResultFlag.ID); } // 建立 resultMapping 物件, 並新增到 resultMappings 中 resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags)); } } // 建立 ResultMapResolver 物件, 該物件可以生成 ResultMap 物件 ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping); try { return resultMapResolver.resolve(); } catch (IncompleteElementExceptione) { // 如果無法建立 ResultMap 物件, 則將該結果新增到 incompleteResultMaps 集合中 configuration.addIncompleteResultMap(resultMapResolver); throw e; } }
2.3 獲取 id
id對於 resultMap 來說是很重要的, 它是一個身份標識。 具有唯一性
// 獲取 ID , 預設值會拼裝所有父節點的 id 或 value 或 property。 String id = resultMapNode.getStringAttribute("id", resultMapNode.getValueBasedIdentifier());
這裡涉及到 XNode
物件中的兩個函式
public String getStringAttribute(String name, String def) { String value = attributes.getProperty(name); if (value == null) { return def; } else { return value; } }
該函式是獲取 XNode 物件對應 XML 節點的 name 屬性值, 如果該屬性不存在, 則返回傳入的預設值 def 。
而在獲取 id 的過程中, 預設值是下面這個函式
/** * 生成元素節點的基礎 id * @return */ public String getValueBasedIdentifier() { StringBuilder builder = new StringBuilder(); XNode current = this; // 當前的節點不為空 while (current != null) { // 如果節點不等於 this, 則在0之前插入 _ 符號, 因為是不斷的獲取父節點的, 因此是插在前面 if (current != this) { builder.insert(0, "_"); } // 獲取 id, id不存在則獲取value, value不存在則獲取 property。 String value = current.getStringAttribute("id", current.getStringAttribute("value", current.getStringAttribute("property", null))); // value 非空, 則將.替換為_, 並將value的值加上 [] if (value != null) { value = value.replace('.', '_'); builder.insert(0, "]"); builder.insert(0, value); builder.insert(0, "["); } // 不管 value 是否存在, 前面都新增上節點的名稱 builder.insert(0, current.getName()); // 獲取父節點 current = current.getParent(); } return builder.toString(); }
該函式是生成元素節點的id, 如果是這樣子的 XML 。
<employee id="${id_var}"> <blah something="that"/> <first_name>Jim</first_name> <last_name>Smith</last_name> <birth_date> <year>1970</year> <month>6</month> <day>15</day> </birth_date> <height units="ft">5.8</height> <weight units="lbs">200</weight> <active>true</active> </employee>
我們呼叫
XNode node = parser.evalNode("/employee/height"); node.getValueBasedIdentifier();
則, 返回值應該是
employee[${id_var}]_height
2.4 解析結果集的型別
結果集的型別, 對應的是一個 JavaBean 物件。 通過反射來獲得該型別。
// 獲取type, type 不存在則獲取 ofType, ofType // 不存在則獲取 resultType, resultType 不存在則獲取 javaType String type = resultMapNode.getStringAttribute("type", resultMapNode.getStringAttribute("ofType", resultMapNode.getStringAttribute("resultType", resultMapNode.getStringAttribute("javaType")))); // ... ... // 獲取 type 對應的 Class 物件 Class<?> typeClass = resolveClass(type);
看原始碼, 有很多個 def 值, 也就是說, 我們在配置結果集的型別的時候都是有優先順序的。 但是, 這裡有一個奇怪的地方, 我原始碼版本(3.5.0-SNAPSHOT)的 <resultMap> 的屬性, 只有 type , 沒有 ofType / resultType / javaType 。 以下為相應的 DTD 約束:
<!ELEMENT resultMap (constructor?,id*,result*,association*,collection*, discriminator?)> <!ATTLIST resultMap id CDATA #REQUIRED type CDATA #REQUIRED extends CDATA #IMPLIED autoMapping (true|false) #IMPLIED >
我懷疑是相容以前的版本。
2.5 獲取繼承結果集和自動對映
String extend = resultMapNode.getStringAttribute("extends"); Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
這個兩個屬性都是在配置 XML 的時候可有可無的。
2.6 解析
先看 DTD 約束
<!ELEMENT resultMap (constructor?,id*,result*,association*,collection*, discriminator?)>
可以有以下幾個子節點:
子節點 | 數量 |
---|---|
constructor | 出現 0 次或 1 次 |
id | 出現 0 次或 多 次 |
result | 出現 0 次或 多 次 |
association | 出現 0 次或 多 次 |
collection | 出現 0 次或 多 次 |
discriminator | 出現 0次或 1 次 |
子節點解析過程很簡單

根據型別進行解析, 最後獲得 resultMappings (List
// 建立一個 resultMappings 的連結串列 List<ResultMapping> resultMappings = new ArrayList<>(); // 將從其他地方傳入的additionalResultMappings新增到該連結串列中 resultMappings.addAll(additionalResultMappings); // 獲取子節點 List<XNode> resultChildren = resultMapNode.getChildren(); // 遍歷解析子節點 for (XNode resultChild : resultChildren) { if ("constructor".equals(resultChild.getName())) { // 解析建構函式元素,其下的沒每一個子節點都會生產一個 ResultMapping 物件 processConstructorElement(resultChild, typeClass, resultMappings); } else if ("discriminator".equals(resultChild.getName())) { // 解析 discriminator 節點 discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings); } else { // 解析其餘的節點 List<ResultFlag> flags = new ArrayList<>(); if ("id".equals(resultChild.getName())) { flags.add(ResultFlag.ID); } resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags)); } }
除了 discriminator 節點, 其餘節點最後都會回到 buildResultMappingFromContext 方法上, 該方法是建立 ResultMapping
物件。
/** * 獲取一行, 如result等, 取得他們所有的屬性, 通過這些屬性建立 ResultMapping 物件 * @param context 對於節點本身 * @param resultType resultMap 的結果型別 * @param flags flag 屬性, 對應 ResultFlag 列舉中的屬性。 一般情況下為空 * @return 返回 ResultMapping * @throws Exception */ private ResultMapping buildResultMappingFromContext(XNode context, Class<?> resultType, List<ResultFlag> flags) throws Exception { String property; // 獲取節點的屬性, 如果節點是建構函式(只有name屬性, 沒有property), // 則獲取的是 name, 否則獲取 property if (flags.contains(ResultFlag.CONSTRUCTOR)) { property = context.getStringAttribute("name"); } else { property = context.getStringAttribute("property"); } String column = context.getStringAttribute("column"); String javaType = context.getStringAttribute("javaType"); String jdbcType = context.getStringAttribute("jdbcType"); String nestedSelect = context.getStringAttribute("select"); // 獲取巢狀的結果集 String nestedResultMap = context.getStringAttribute("resultMap", processNestedResultMappings(context, Collections.<ResultMapping> emptyList())); String notNullColumn = context.getStringAttribute("notNullColumn"); String columnPrefix = context.getStringAttribute("columnPrefix"); String typeHandler = context.getStringAttribute("typeHandler"); String resultSet = context.getStringAttribute("resultSet"); String foreignColumn = context.getStringAttribute("foreignColumn"); boolean lazy = "lazy".equals(context.getStringAttribute("fetchType", configuration.isLazyLoadingEnabled() ? "lazy" : "eager")); // 以上獲取各個屬性節點 // 解析 javaType, typeHandler, jdbcType Class<?> javaTypeClass = resolveClass(javaType); @SuppressWarnings("unchecked") Class<? extends TypeHandler<?>> typeHandlerClass = (Class<? extends TypeHandler<?>>) resolveClass(typeHandler); JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType); // 建立resultMapping物件 return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect, nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resultSet, foreignColumn, lazy); }
如果是 discriminator , 則處理該元素並建立鑑別器。
/** * 處理鑑別器 * @param context 節點 * @param resultType 結果型別 * @param resultMappings 列結果集合 * @return 鑑別器 * @throws Exception */ private Discriminator processDiscriminatorElement(XNode context, Class<?> resultType, List<ResultMapping> resultMappings) throws Exception { String column = context.getStringAttribute("column"); String javaType = context.getStringAttribute("javaType"); String jdbcType = context.getStringAttribute("jdbcType"); String typeHandler = context.getStringAttribute("typeHandler"); // 先獲取各個屬性 // 取得 javaType 對應的型別 Class<?> javaTypeClass = resolveClass(javaType); // 取得 typeHandler 對應的型別 @SuppressWarnings("unchecked") Class<? extends TypeHandler<?>> typeHandlerClass = (Class<? extends TypeHandler<?>>) resolveClass(typeHandler); // 取得 jdbcType 對應的型別 JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType); // 建立 discriminatorMap, 並遍歷子節點, 以 value->resultMap 的方式放入discriminatorMap中 Map<String, String> discriminatorMap = new HashMap<>(); for (XNode caseChild : context.getChildren()) { String value = caseChild.getStringAttribute("value"); String resultMap = caseChild.getStringAttribute("resultMap", processNestedResultMappings(caseChild, resultMappings)); discriminatorMap.put(value, resultMap); } // 建立鑑別器 return builderAssistant.buildDiscriminator(resultType, column, javaTypeClass, jdbcTypeEnum, typeHandlerClass, discriminatorMap); }
鑑別器內部, 也是含有 ResultMapping 的
public class Discriminator { private ResultMapping resultMapping; private Map<String, String> discriminatorMap; ...... }
2.7 建立 ResultMap 物件
在解析完 <resultMap> 的各個屬性和子節點之後。 建立 ResultMapResolver
物件, 通過物件可以生成 ResultMap
。
/** * 建立並新增 ResultMap 到 Configuration 物件中 * @param id id, 配置了 id 可以提高效率 * @param type 型別 * @param extend 繼承 * @param discriminator 鑑別器 * @param resultMappings 列集 * @param autoMapping 是否自動對映 * @return 返回建立的 ResultMap 物件 */ public ResultMap addResultMap( String id, Class<?> type, String extend, Discriminator discriminator, List<ResultMapping> resultMappings, Boolean autoMapping) { id = applyCurrentNamespace(id, false); extend = applyCurrentNamespace(extend, true); if (extend != null) { if (!configuration.hasResultMap(extend)) { throw new IncompleteElementException("Could not find a parent resultmap with id '" + extend + "'"); } // 從 configuration 中獲取繼承的結果集 ResultMap resultMap = configuration.getResultMap(extend); // 獲取所整合結果集的所有 ResultMapping 集合 List<ResultMapping> extendedResultMappings = new ArrayList<>(resultMap.getResultMappings()); // 移除需要覆蓋的 ResultMapping 集合 extendedResultMappings.removeAll(resultMappings); // 如果該 resultMap 中定義了構造節點, 則移除其父節點的構造器 boolean declaresConstructor = false; for (ResultMapping resultMapping : resultMappings) { if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) { declaresConstructor = true; break; } } if (declaresConstructor) { Iterator<ResultMapping> extendedResultMappingsIter = extendedResultMappings.iterator(); while (extendedResultMappingsIter.hasNext()) { if (extendedResultMappingsIter.next().getFlags().contains(ResultFlag.CONSTRUCTOR)) { extendedResultMappingsIter.remove(); } } } // 新增需要被繼承的 ResultMapping 集合 resultMappings.addAll(extendedResultMappings); } // 通過建造者模式建立 ResultMap 物件 ResultMap resultMap = new ResultMap.Builder(configuration, id, type, resultMappings, autoMapping) .discriminator(discriminator) .build(); // 新增到 Configuration 物件中 configuration.addResultMap(resultMap); return resultMap; }
3 解析結果
有如下的資料庫表

通過程式碼生成器生成 XML 和 Mapper。
新增結果集
對應的 sql
則最後解析出的結果
4 一起來學習 mybatis
你想不想來學習 mybatis? 學習其使用和原始碼呢?那麼, 在部落格園關注我吧!!
我自己打算把這個原始碼系列更新完畢, 同時會更新相應的註釋。快去 star 吧!!