mybatis原始碼學習------resultMap和sql片段的解析
resultMap的解析
書接上回,mybatis對於<resultMap></resultMap>
標籤解析的方法入口為:
resultMapElements(List list)
根據mybatis-3-mapper.dtd檔案中對於resultMap的定義可知,一個mapper節點內可以定義任意多個resultMap節點,所以resultMapElements方法會遍歷所有的resultMap進行解析,程式碼如下:
private void resultMapElements(List<XNode> list) {
for (XNode resultMapNode : list) {
try {
resultMapElement(resultMapNode);
} catch (IncompleteElementException e) {
// ignore, it will be retried
}
}
}
resultMapElement(XNode)
呼叫過載方法,三個引數的resultMapElement方法主要用於後續遞迴呼叫,所以這裡單引數的resultMapElement方法更像是一個“橋接”方法。
private ResultMap resultMapElement(XNode resultMapNode) {
return resultMapElement(resultMapNode, Collections.emptyList(), null);
}
resultMapElement(XNod, List, Class)方法
resultMapElement方法會為<resultMap></resultMap>
中配置的每一個或標籤都建立一個ResultMapping物件,並將其儲存在一個集合中,最後傳遞給ResultMapResolver物件進行解析。
private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings, Class<?> enclosingType) {
ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
//因為<resultMap/>標籤的配置存在巢狀的情況,所以在程式碼處理巢狀情況的時候一定會使用遞迴,
//這裡獲取預設值的邏輯主要是為了處理<resultMap/>巢狀<resultMap/>的情況
String type = resultMapNode.getStringAttribute("type",
resultMapNode.getStringAttribute("ofType",
resultMapNode.getStringAttribute("resultType",
resultMapNode.getStringAttribute("javaType"))));
//解析type屬性所配置的類
Class<?> typeClass = resolveClass(type);
if (typeClass == null) {
typeClass = inheritEnclosingType(resultMapNode, enclosingType);
}
Discriminator discriminator = null; //resultMappings集合中維護了當前<resultMap/>中所有的對映關係
List<ResultMapping> resultMappings = new ArrayList<>(additionalResultMappings);
List<XNode> resultChildren = resultMapNode.getChildren();
for (XNode resultChild : resultChildren) {
if ("constructor".equals(resultChild.getName())) {//處理構造器
processConstructorElement(resultChild, typeClass, resultMappings);
} else if ("discriminator".equals(resultChild.getName())) {//處理鑑別器
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));
}
}
//獲取resultMap節點配置的ID
//getValueBasedIdentifier()方法返回的格式為 型別1[ID1]_型別2[ID2]......
String id = resultMapNode.getStringAttribute("id",
resultMapNode.getValueBasedIdentifier());
//獲取resultMap節點配置的繼承關係
String extend = resultMapNode.getStringAttribute("extends");
//獲取是否開啟自動對映配置
Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
//建立ResultMap解析器例項
ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
try {
//將具體的解析任務委派給MapperBuilderAssistant的例項
return resultMapResolver.resolve();
} catch (IncompleteElementException e) {
configuration.addIncompleteResultMap(resultMapResolver);
throw e;
}
}
解析<constructor/>
processConstructorElement方法對建構函式節點中的ID進行了特殊的標識,便於在後續的解析中區分對id和idArg進行區分,如下圖所示,:
private void processConstructorElement(XNode resultChild, Class<?> resultType, List<ResultMapping> resultMappings) {
List<XNode> argChildren = resultChild.getChildren();
for (XNode argChild : argChildren) {
List<ResultFlag> flags = new ArrayList<>();
//進行標記
flags.add(ResultFlag.CONSTRUCTOR);
if ("idArg".equals(argChild.getName())) {
flags.add(ResultFlag.ID);
}
resultMappings.add(buildResultMappingFromContext(argChild, resultType, flags));
}
}
呼叫buildResultMappingFromContext方法進行解析
buildResultMappingFromContext方法
從功能上說,本方法只負責將使用者在xml中配置的資訊讀取出來,解析的部分則交給MapperBuilderAssistant#buildResultMapping去完成
private ResultMapping buildResultMappingFromContext(XNode context, Class<?> resultType, List<ResultFlag> flags) {
String property;
if (flags.contains(ResultFlag.CONSTRUCTOR)) {
//獲取構造方法形參的名字的配置。從 3.4.3 版本開始,通過指定具體的引數名,你可以以任意順序寫入 arg 元素
property = context.getStringAttribute("name");
} else {
//獲取對映到列結果的欄位或屬性的配置
property = context.getStringAttribute("property");
}
//獲取資料庫中的列名,或者是列的別名的配置
String column = context.getStringAttribute("column");
//獲取Java類的全限定名,或一個型別別名的配置
String javaType = context.getStringAttribute("javaType");
//獲取對應的JDBC型別的配置
String jdbcType = context.getStringAttribute("jdbcType");
//獲取巢狀查詢對映語句的ID
String nestedSelect = context.getStringAttribute("select");
//獲取巢狀結果集對映的ID,
String nestedResultMap = context.getStringAttribute("resultMap", () ->
processNestedResultMappings(context, Collections.emptyList(), resultType));
//使用者指定非空的列
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對應的型別
Class<?> javaTypeClass = resolveClass(javaType);
//根據使用者配置解析typeHandler對應的型別
Class<? extends TypeHandler<?>> typeHandlerClass = resolveClass(typeHandler);
//根據使用者配置解析jdbcType對應的型別
JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
//委派給BuilderAssistant例項對其進行進一步的解析
return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect, nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resultSet, foreignColumn, lazy);
}
MapperBuilderAssistant#buildResultMapping
該方法主要對上一步解析出來的配置進行例項化,之後再呼叫ResultMapping.Builder#build()方法對使用者的配置做進一步的解析並建立對應的ResultMapping物件。
public ResultMapping buildResultMapping(
Class<?> resultType,
String property,
String column,
Class<?> javaType,
JdbcType jdbcType,
String nestedSelect,
String nestedResultMap,
String notNullColumn,
String columnPrefix,
Class<? extends TypeHandler<?>> typeHandler,
List<ResultFlag> flags,
String resultSet,
String foreignColumn,
boolean lazy) {
//對結果型別進行型別推斷
Class<?> javaTypeClass = resolveResultJavaType(resultType, property, javaType);
//對使用者配置的型別攔截器進行例項化
TypeHandler<?> typeHandlerInstance = resolveTypeHandler(javaTypeClass, typeHandler);
//composites集合主要儲存column屬性拆分後生成的結果,在巢狀查詢的模式下,column的配置會作為引數傳遞給目標select語句
List<ResultMapping> composites;
if ((nestedSelect == null || nestedSelect.isEmpty()) && (foreignColumn == null || foreignColumn.isEmpty())) {
composites = Collections.emptyList();
} else {
composites = parseCompositeColumnName(column);
}
//使用建造者設計模式建立ResultMapping例項
return new ResultMapping.Builder(configuration, property, column, javaTypeClass)
.jdbcType(jdbcType)
.nestedQueryId(applyCurrentNamespace(nestedSelect, true))
.nestedResultMapId(applyCurrentNamespace(nestedResultMap, true))
.resultSet(resultSet)
.typeHandler(typeHandlerInstance)
.flags(flags == null ? new ArrayList<>() : flags)
.composites(composites)
.notNullColumns(parseMultipleColumnNames(notNullColumn))
.columnPrefix(columnPrefix)
.foreignColumn(foreignColumn)
.lazy(lazy)
.build();
}
ResultMapping.Builder#build()方法
該方法的主要邏輯是對使用者的配置進行各種校驗,因為無論當前解析的是
public ResultMapping build() {
// lock down collections
resultMapping.flags = Collections.unmodifiableList(resultMapping.flags);
resultMapping.composites = Collections.unmodifiableList(resultMapping.composites);
//如果使用者沒有配置對應的TypeHandler,則mybatis會推斷具體的型別攔截器
resolveTypeHandler();
//各種校驗
validate();
return resultMapping;
}
validate()方法的定義如下,跟我寫的程式碼一樣拉胯,就不做分析了:
解析 <discriminator>
官網中對於鑑別器的作用的描述如下:
有時候,一個資料庫查詢可能會返回多個不同的結果集(但總體上還是有一定的聯絡的)。 鑑別器(discriminator)元素就是被設計來應對這種情況的,另外也能處理其它情況,例如類的繼承層次結構。 鑑別器的概念很好理解——它很像 Java 語言中的 switch 語句。
解析discriminator的方法為processDiscriminatorElement方法
mybatis在解析discriminator時會建立一個Map來儲存其各個case所對應的resultMap物件,示意圖如下:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-JqzK9U8W-1604318520352)(/Users/lijiaxing/github/mybatis-3-master/doc/build/解析discriminator的示意圖.png)]
//解析鑑別器配置
private Discriminator processDiscriminatorElement(XNode context, Class<?> resultType, List<ResultMapping> resultMappings) {
String column = context.getStringAttribute("column");
String javaType = context.getStringAttribute("javaType");
String jdbcType = context.getStringAttribute("jdbcType");
String typeHandler = context.getStringAttribute("typeHandler");
Class<?> javaTypeClass = resolveClass(javaType);
Class<? extends TypeHandler<?>> typeHandlerClass = resolveClass(typeHandler);
JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
Map<String, String> discriminatorMap = new HashMap<>();
for (XNode caseChild : context.getChildren()) {
String value = caseChild.getStringAttribute("value");
//處理巢狀結果對映
String resultMap = caseChild.getStringAttribute("resultMap", processNestedResultMappings(caseChild, resultMappings, resultType));
discriminatorMap.put(value, resultMap);
}
//呼叫MapperBuilderAssistant#buildDiscriminator方法來構建Discriminatorshili
return builderAssistant.buildDiscriminator(resultType, column, javaTypeClass, jdbcTypeEnum, typeHandlerClass, discriminatorMap);
}
processNestedResultMappings方法
該方法會建立鑑別器map集合中value所指定的resultMap物件
//處理巢狀結果對映
private String processNestedResultMappings(XNode context, List<ResultMapping> resultMappings, Class<?> enclosingType) {
if (Arrays.asList("association", "collection", "case").contains(context.getName())
&& context.getStringAttribute("select") == null) {
//校驗相關規則
validateCollection(context, enclosingType);
//校驗通過後解析對應的resultMap節點
ResultMap resultMap = resultMapElement(context, resultMappings, enclosingType);
return resultMap.getId();
}
return null;
}
MapperBuilderAssistant#buildDiscriminator
public Discriminator buildDiscriminator(
Class<?> resultType,
String column,
Class<?> javaType,
JdbcType jdbcType,
Class<? extends TypeHandler<?>> typeHandler,
Map<String, String> discriminatorMap) {
//構建ResultMapping物件
ResultMapping resultMapping = buildResultMapping(
resultType,
null,
column,
javaType,
jdbcType,
null,
null,
null,
null,
typeHandler,
new ArrayList<>(),
null,
null,
false);
Map<String, String> namespaceDiscriminatorMap = new HashMap<>();
//遍歷map的value,為每一個resultMap的id加上namespace
for (Map.Entry<String, String> e : discriminatorMap.entrySet()) {
String resultMap = e.getValue();
resultMap = applyCurrentNamespace(resultMap, true);
namespaceDiscriminatorMap.put(e.getKey(), resultMap);
}
//通過建造者模式建立Discriminator物件
return new Discriminator.Builder(configuration, resultMapping, namespaceDiscriminatorMap).build();
}
解析其他標籤
處理其他情況,如<id>
、<result>
、<association>
等標籤的程式碼如下,呼叫buildResultMappingFromContext方法建立對應的ResultMapping物件,並將其儲存在對應的list中即可。
建立並儲存ResultMap物件
回顧resultMapElement方法的邏輯,將配置解析為ResultMapping物件並新增到集合中後,接下來要做的就是建立對應的ResultMap物件,並儲存在configuration中。
private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings, Class<?> enclosingType) {
//省略
//獲取resultMap節點配置的ID
//getValueBasedIdentifier()方法返回的格式為 型別1[ID1]_型別2[ID2]......
//用於處理resultMap巢狀時,內層resultMap沒有id的情況
String id = resultMapNode.getStringAttribute("id",
resultMapNode.getValueBasedIdentifier());
//獲取resultMap節點配置的繼承關係
String extend = resultMapNode.getStringAttribute("extends");
//獲取是否開啟自動對映配置
Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
//建立ResultMap解析器例項
ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
try {
//將具體的解析任務委派給MapperBuilderAssistant的例項
return resultMapResolver.resolve();
} catch (IncompleteElementException e) {
configuration.addIncompleteResultMap(resultMapResolver);
throw e;
}
}
由上面的邏輯可知,XmlMapperBuilder在這裡建立了一個ResultMapResolver物件,並通過該物件來完成ResultMap物件建立和儲存工作。
ResultMapResolver類的定義
public class ResultMapResolver {
private final MapperBuilderAssistant assistant;
private final String id;
private final Class<?> type;
private final String extend;
private final Discriminator discriminator;
private final List<ResultMapping> resultMappings;
private final Boolean autoMapping;
public ResultMapResolver(MapperBuilderAssistant assistant, String id, Class<?> type, String extend, Discriminator discriminator, List<ResultMapping> resultMappings, Boolean autoMapping) {
this.assistant = assistant;
this.id = id;
this.type = type;
this.extend = extend;
this.discriminator = discriminator;
this.resultMappings = resultMappings;
this.autoMapping = autoMapping;
}
public ResultMap resolve() {
//委派給MapperBuilderAssistant例項完成建立和儲存的工作
return assistant.addResultMap(this.id, this.type, this.extend, this.discriminator, this.resultMappings, this.autoMapping);
}
}
MapperBuilderAssistant#addResultMap
public ResultMap addResultMap(
String id,
Class<?> type,
String extend,
Discriminator discriminator,
List<ResultMapping> resultMappings,
Boolean autoMapping) {
//將ID替換為帶有名稱空間的格式
id = applyCurrentNamespace(id, false);
//將當前resultMap所繼承的resultMap的ID替換為帶有名稱空間的格式
extend = applyCurrentNamespace(extend, true);
//處理繼承樹
if (extend != null) {
if (!configuration.hasResultMap(extend)) {
throw new IncompleteElementException("Could not find a parent resultmap with id '" + extend + "'");
}
ResultMap resultMap = configuration.getResultMap(extend);
//獲取父節點中配置的所有ResultMapping物件
List<ResultMapping> extendedResultMappings = new ArrayList<>(resultMap.getResultMappings());
//移除重複的ResultMapping配置
extendedResultMappings.removeAll(resultMappings);
//如果子resultMap節點中宣告瞭<constructor><constructor/>標籤,則需要將父節點中的構造器標籤配置刪除
boolean declaresConstructor = false;
for (ResultMapping resultMapping : resultMappings) {
if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) {
declaresConstructor = true;
break;
}
}
if (declaresConstructor) {
//移除父節點中的構造器配置
extendedResultMappings.removeIf(resultMapping -> resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR));
}
//合併父子<resultMap>節點的ResultMapping配置
resultMappings.addAll(extendedResultMappings);
}
//通過建造者設計模式建立ResultMap物件,並將其新增到mybatis的全域性配置物件中
ResultMap resultMap = new ResultMap.Builder(configuration, id, type, resultMappings, autoMapping)
.discriminator(discriminator)
.build();
configuration.addResultMap(resultMap);
return resultMap;
}
Configuration#addResultMap
後面兩個方法暫時沒看懂是什麼意思,先挖個坑,後面再來填
public void addResultMap(ResultMap rm) {
resultMaps.put(rm.getId(), rm);
//TODO
checkLocallyForDiscriminatedNestedResultMaps(rm);
//TODO
checkGloballyForDiscriminatedNestedResultMaps(rm);
}
sql片段的解析
解析sql片段的程式碼入口為XmlMapperBuilder#sqlElement
sqlElement
private void sqlElement(List<XNode> list) {
if (configuration.getDatabaseId() != null) {
sqlElement(list, configuration.getDatabaseId());
}
sqlElement(list, null);
}
//如果使用者配置了databaseId,則會檢測當前databaseId與configuration中配置的databaseId是否一致,
//一致則新增到sqlFragments屬性中
private void sqlElement(List<XNode> list, String requiredDatabaseId) {
for (XNode context : list) {
String databaseId = context.getStringAttribute("databaseId");
String id = context.getStringAttribute("id");
//給id拼接namespace
id = builderAssistant.applyCurrentNamespace(id, false);
if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) {
//如果匹配則將其新增到sqlFragments欄位中
sqlFragments.put(id, context);
}
}
}
databaseIdMatchesCurrent
//資料庫廠商標識是否與當前sql片段的定義相同
private boolean databaseIdMatchesCurrent(String id, String databaseId, String requiredDatabaseId) {
//如果需要的資料庫廠商ID不為空
if (requiredDatabaseId != null) {
return requiredDatabaseId.equals(databaseId);
}
//隱含條件為需要的資料庫廠商ID為空
if (databaseId != null) {
return false;
}
//如果sqlFragments集合中不存在對應的sql片段定義
if (!this.sqlFragments.containsKey(id)) {
return true;
}
//如果存在ID對應的sql片段
XNode context = this.sqlFragments.get(id);
//根據上面的if語句可以得出以下,如果程式能走到這裡,則requiredDatabaseId一定為null,故直接判斷databaseId屬性的值是否為null即可
return context.getStringAttribute("databaseId") == null;
}
相關文章
- mybatis原始碼學習------cache-ref和cache的解析MyBatis原始碼
- MyBatis學習 之 二、SQL語句對映檔案(1)resultMapMyBatisSQL
- mybatis之sql查詢配置檔案resultType和resultMapMyBatisSQL
- 【Mybatis系列】從原始碼角度理解Mybatis欄位對映-AS&ResultMapMyBatis原始碼
- Mybatis 原始碼學習(二)MyBatis原始碼
- MyBatis原始碼解析MyBatis原始碼
- Mybatis原始碼解析之執行SQL語句MyBatis原始碼SQL
- mybatis plugin原始碼解析MyBatisPlugin原始碼
- Golang 學習——error 和建立 error 原始碼解析GolangError原始碼
- Mybatis原始碼分析(二)XML的解析和Annotation的支援MyBatis原始碼XML
- 深入Mybatis原始碼——配置解析MyBatis原始碼
- Mybatis原始碼解析4——SqlSessionMyBatis原始碼SQLSession
- 【MyBatis原始碼解析】MyBatis一二級快取MyBatis原始碼快取
- mybatis原始碼-註解sqlMyBatis原始碼SQL
- myBatis——註解,#{}與${},resultMap的使用MyBatis
- myBatis原始碼解析-反射篇(4)MyBatis原始碼反射
- MyBatis詳細原始碼解析(上篇)MyBatis原始碼
- mybatis原始碼學習:從SqlSessionFactory到代理物件的生成MyBatis原始碼SQLSession物件
- 【Mybatis原始碼解析】- JDBC連線資料庫的原理和操作MyBatis原始碼JDBC資料庫
- mybatis原始碼學習:一級快取和二級快取分析MyBatis原始碼快取
- Spring原始碼深度解析(郝佳)-學習-原始碼解析-Spring MVCSpring原始碼MVC
- myBatis原始碼解析-日誌篇(1)MyBatis原始碼
- myBatis原始碼解析-快取篇(2)MyBatis原始碼快取
- Mybatis原始碼解析2—— 例項搭建MyBatis原始碼
- mybatis通用mapper原始碼解析(二)MyBatisAPP原始碼
- mybatis通用mapper原始碼解析(一)MyBatisAPP原始碼
- mybatis原始碼解析(四)--- MapperStatement的註冊MyBatis原始碼APP
- MyBatis原始碼學習筆記(一) 初遇篇MyBatis原始碼筆記
- 【spring原始碼學習】spring的事務管理的原始碼解析Spring原始碼
- 不學無數——Mybatis解析判斷表示式原始碼分析MyBatis原始碼
- mybatis原始碼解讀---一條sql的旅程MyBatis原始碼SQL
- MyBatis原始碼窺探(一):MyBatis整體架構解析MyBatis原始碼架構
- 【Mybatis系列】從原始碼角度理解Mybatis的$和#的作用MyBatis原始碼
- Mybatis 輸出對映-- resultType resultMapMyBatis
- (一) Mybatis原始碼分析-解析器模組MyBatis原始碼
- mybatis原始碼解析-日誌介面卡MyBatis原始碼
- 從原始碼的角度解析Mybatis的會話機制原始碼MyBatis會話
- Spark SQL原始碼解析(四)Optimization和Physical Planning階段解析SparkSQL原始碼