resultMap完美解析(含github實例)

作者:阿進的寫字檯

cnblogs.com/homejim/p/9853703.html

在 select 語句中查詢得到的是一張二維表, 水平方向上看是一個個字段, 垂直方向上看是一條條記錄。作為面向對象的語言, Java 中的的對象是根據類定義創建的。 類之間的引用關係可以認為是嵌套的關係。

在 mybatis 中, resultMap 節點定義了結果集和結果對象(JavaBean)之間的映射規則。

本文主要講解的是 resultMap 的解析。

resultMap完美解析(含github實例)

兩個基礎類

在閱讀本文之前, 最好能對這兩個類有相應的理解。

1.1、列映射類ResultMapping

ResultMapping 對象記錄了結果集中一列與隊友JavaBean中一個屬性的對應關係

resultMap完美解析(含github實例)

解析

2.1、入口函數

resultMap 是 mapper.xml 文件下的, 因此其是解析 Mapper 的一個環節。

resultMapElements(context.evalNodes("/mapper/resultMap"));

解析, 由於是可以有多個的, 因此, context.evalNodes("/mapper/resultMap")返回的是一個 List。

private void resultMapElements(List list) throws Exception {
 // 遍歷, 解析
 for (XNode resultMapNode : list) {
 try {
 resultMapElement(resultMapNode);
 } catch (IncompleteElementException e) {
 // ignore, it will be retried
 }
 }
 }

2.2、整個過程就是 resultMapElement 這個函數。

其流程大體如下:

resultMap完美解析(含github實例)

對應的代碼

private ResultMap resultMapElement(XNode resultMapNode) throws Exception {
 return resultMapElement(resultMapNode, Collections. emptyList());
}
 /**
 * 處理  節點, 將節點解析成 ResultMap 對象, 下面包含有 ResultMapping 對象組成的列表
 * @param resultMapNode resultMap 節點
 * @param additionalResultMappings 另外的 ResultMapping 列
 * @return ResultMap 對象
 * @throws Exception
 */
 private ResultMap resultMapElement(XNode resultMapNode, List 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 resultMappings = new ArrayList<>();
 resultMappings.addAll(additionalResultMappings);
 // 處理子節點
 List 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 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 (IncompleteElementException e) {
 // 如果無法創建 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。

resultMap完美解析(含github實例)

而在獲取 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。

 
 Jim
 Smith
 
 1970
 6
 15
 
 5.8
 200
 true

我們調用

 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)的 的屬性, 只有 type, 沒有 ofType/resultType/javaType。 以下為相應的 DTD 約束:


我懷疑是兼容以前的版本。

2.5、獲取繼承結果集和自動映射

 String extend = resultMapNode.getStringAttribute("extends");
 Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");

這個兩個屬性都是在配置 XML 的時候可有可無的。

resultMap完美解析(含github實例)

2.6、解析

先看 DTD 約束

可以有以下幾個子節點:


resultMap完美解析(含github實例)


resultMap完美解析(含github實例)

根據類型進行解析, 最後獲得 resultMappings

 // 創建一個 resultMappings 的鏈表
 List resultMappings = new ArrayList<>();
 // 將從其他地方傳入的additionalResultMappings添加到該鏈表中
 resultMappings.addAll(additionalResultMappings);
 // 獲取子節點
 List 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 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 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. 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 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 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 discriminatorMap;
 ......
}

2.7、創建 ResultMap 對象

在解析完 的各個屬性和子節點之後。 創建 ResultMapResolver 對象, 通過對象可以生成 ResultMap。

resultMap完美解析(含github實例)

/**
 * 創建並添加 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 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 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 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;
 }

解析結果

有如下的數據庫表

resultMap完美解析(含github實例)

resultMap完美解析(含github實例)

通過代碼生成器生成 XML 和 Mapper。

添加結果集

resultMap完美解析(含github實例)

對應的 sql

resultMap完美解析(含github實例)

則最後解析出的結果

resultMap完美解析(含github實例)


分享到:


相關文章: