鑒于processNestedResultMappings()
后面的實現遞歸調用了resultMapElement()
方法,所以我們繼續回到buildResultMappingFromContext()
方法的解析過程中來.
// 默認情況下,子對象僅在至少一個列映射到其屬性非空時才創建。
// 通過對這個屬性指定非空的列將改變默認行為,這樣做之后Mybatis將僅在這些列非空時才創建一個子對象。
// 可以指定多個列名,使用逗號分隔。默認值:未設置(unset)。
String notNullColumn = context.getStringAttribute("notNullColumn");
// 當連接多表時,你將不得不使用列別名來避免ResultSet中的重復列名。
// 因此你可以指定columnPrefix映射列名到一個外部的結果集中。
String columnPrefix = context.getStringAttribute("columnPrefix");
// 類型轉換處理器
String typeHandler = context.getStringAttribute("typeHandler");
// 獲取resultSet集合
String resultSet = context.getStringAttribute("resultSet");
// 標識出包含foreign keys的列的名稱
String foreignColumn = context.getStringAttribute("foreignColumn");
// 懶加載
boolean lazy = "lazy".equals(context.getStringAttribute("fetchType", configuration.isLazyLoadingEnabled() ? "lazy" : "eager"));
// 解析java類型
Class<?> javaTypeClass = resolveClass(javaType);
// 解析類型處理器
Class<? extends TypeHandler<?>> typeHandlerClass = resolveClass(typeHandler);
// 解析出jdbc類型
JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
在完成了對resultMap
的處理之后,接下來buildResultMappingFromContext()
方法會依次獲取元素的notNullColumn
,columnPrefix
,typeHandler
,resultSet
,foreignColumn
,fetchType
屬性配置,并轉換成具體需要使用的類型。
這些屬性并不是全都存在于元素的屬性定義中,可能某一個元素只具有其中部分屬性定義,甚至完全不包含這幾個屬性定義.
上面幾個屬性的處理只是簡單的取值,相對來說值得注意的是懶加載屬性配置的實現,在這里,我們看到結果映射的懶加載配置會覆蓋全局懶加載配置:
// 懶加載
boolean lazy = "lazy".equals(context.getStringAttribute("fetchType", configuration.isLazyLoadingEnabled() ? "lazy" : "eager"));
在得到上面這些屬性定義之后,mybatis
就會將這些屬性傳遞給MapperBuilderAssistant
的buildResultMapping()
方法來完成一個ResultMapping
對象的創建工作.
學到這里,我們先停止繼續解析buildResultMapping()
方法的欲望,回頭來看一下鑒別器配置discriminator
元素的解析操作.
鑒別器配置的解析處理
discriminator
元素的解析操作由processDiscriminatorElement(XNode context, Class<?> resultType, List<ResultMapping> resultMappings)
方法來完成:
if ("discriminator".equals(resultChild.getName())) {
// 處理discriminator節點(鑒別器)
// 通過配置discriminator節點可以實現根據查詢結果動態生成查詢語句的功能
discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
}
processDiscriminatorElement()
方法的實現并不復雜,mybatis
解析discriminator
元素時,會依次獲取他對應的column
(字段名稱),javaType
(java
類型),jdbcType
(jdbc
類型),typeHandler
(類型轉換處理器定義)屬性定義.
然后通過別名機制解析出來具體的java
/jdbc
/類型轉換處理器類型,再遍歷處理每一個case
子元素的定義:
/**
* 解析鑒別器
*
* @param context 鑒別器上下文
* @param resultType 返回類型
* @param resultMappings 已有的ResultMap集合
*/
private Discriminator processDiscriminatorElement(XNode context, Class<?> resultType, List<ResultMapping> resultMappings) throws Exception {
// 獲取字段名稱
String column = context.getStringAttribute("column");
// 獲取java類型
String javaType = context.getStringAttribute("javaType");
// 獲取jdbc類型
String jdbcType = context.getStringAttribute("jdbcType");
// 獲取類型處理器
String typeHandler = context.getStringAttribute("typeHandler");
// 獲取真實的java類型
Class<?> javaTypeClass = resolveClass(javaType);
// 獲取真實的類型處理器
Class<? extends TypeHandler<?>> typeHandlerClass = resolveClass(typeHandler);
// 獲取真實的jdbc類型
JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
// 處理鑒別器
Map<String, String> discriminatorMap = new HashMap<>();
for (XNode caseChild : context.getChildren()) {
// 解析case代碼塊
// 解析case代碼塊的value標記
String value = caseChild.getStringAttribute("value");
// 解析case代碼塊的ResultMap標記
String resultMap = caseChild.getStringAttribute("resultMap" /*使用指定的resultMap*/
, processNestedResultMappings(caseChild, resultMappings, resultType/*如果沒有指定resultMap,則動態生成ResultMap實例*/
)
);
// 鑒別器存放值和resultMap的對應關系
discriminatorMap.put(value, resultMap);
}
// 構造鑒別器
return builderAssistant.buildDiscriminator(
resultType /*返回類型*/
, column /*對應的字段*/
, javaTypeClass /*字段類型*/
, jdbcTypeEnum /*jdbc類型*/
, typeHandlerClass/*類型轉換處理器*/
, discriminatorMap /*鑒別器映射集合*/
);
}
負責解析case
元素的方法是processNestedResultMappings()
方法,該方法我們在前面已經講過了,他負責解析嵌套結果映射配置,并返回嵌套結果映射對應的ResultMap
對象的全局引用ID
.
需要注意的是,在調用processNestedResultMappings()
方法時,傳入的resultMappings
集合,該參數是從外部傳入的:
String resultMap = caseChild.getStringAttribute("resultMap" /*使用指定的resultMap*/
, processNestedResultMappings(caseChild, resultMappings, resultType/*如果沒有指定resultMap,則動態生成ResultMap實例*/
)
);
如果我們追根溯源,會發現該集合保存的是discriminator
元素的同級元素所對應的ResultMapping
對象.
前面說過,根據DTD
定義,為具有resultMap
性質的元素配置discriminator
子元素時,discriminator
子元素必須聲明在元素的尾部:
因此在解析具有resultMap
性質的元素時,它的discriminator
子元素一定是最后一個被解析的,所以上面方法調用傳入的resultMappings
集合保存的就是具有resultMap
性質的元素的除discriminator
子元素之外的所有子元素定義.
這個resultMappings
集合對象,最后會在方法調用中傳入到resultMapElement()
方法中,這也是為什么前面我們說:
additionalResultMappings
表示現有的ResultMapping
集合,該參數只有在解析discriminator
元素時才有數據,其他時候均為空集合.
這個參數到這里就對應上了.
在processDiscriminatorElement()
方法中聲明了一個類型為Map<String, String>
的discriminatorMap
集合,該集合存放的是case
元素所匹配的數據值以及該值對應的ResultMap
對象的引用ID
.
當我們獲取到discriminator
中每一個case
子元素的定義之后,Mybatis
就會委托映射器構建助手MapperBuilderAssistant
的buildDiscriminator()
方法來生成Discriminator
對象。
Discriminator
對應著mybatis
中的discriminator
元素定義,他只有兩個參數,一個參數用來存儲他對應的ResultMapping
對象,另一個參數則存儲著discriminator
的case
元素配置的鑒別器指定字段的值和resultMap的關聯關系。
這是Discriminator
的基本定義:
/**
* 鑒別器,每一個discriminator節點都對應一個鑒別器
*
* @author Clinton Begin
*/
public class Discriminator {
/**
* resultMap對象
* 所有的<result>節點
*/
private ResultMapping resultMapping;
/**
* 鑒別器指定字段的值和resultMap的關聯關系
* if then 的映射
*/
private Map<String, String> discriminatorMap;
Discriminator() {
}
// ...省略...
}
在映射器構建助手的buildDiscriminator()
方法中首先會使用discriminator
元素中配置的幾個屬性,生成對應的ResultMapping
對象,具體的ResultMapping
對象的生成過程,是由buildResultMapping
方法來完成的,這個方法我們前面提到過,后面會統一介紹.
/**
* 構造Discriminator對象
*
* @param resultType 返回類型
* @param column 列名稱
* @param javaType java類型
* @param jdbcType jdbc類型
* @param typeHandler 類型轉換處理器
* @param discriminatorMap 鑒別器指定字段的值和resultMap的關聯關系
* @return Discriminator對象
*/
public Discriminator buildDiscriminator(
Class<?> resultType,
String column,
Class<?> javaType,
JdbcType jdbcType,
Class<? extends TypeHandler<?>> typeHandler,
Map<String, String> discriminatorMap) {
// 構建resultMap
ResultMapping resultMapping = buildResultMapping(
resultType,
null,
column,
javaType,
jdbcType,
null,
null,
null,
null,
typeHandler,
new ArrayList<ResultFlag>(),
null,
null,
false);
Map<String, String> namespaceDiscriminatorMap = new HashMap<>();
// 循環處理所有的case元素的定義
for (Map.Entry<String, String> e : discriminatorMap.entrySet()) {
String resultMap = e.getValue();
// 拼接命名空間
resultMap = applyCurrentNamespace(resultMap, true);
// 更新鑒別器和resultMap的關系
namespaceDiscriminatorMap.put(e.getKey(), resultMap);
}
// 構建鑒別器
return new Discriminator.Builder(configuration, resultMapping, namespaceDiscriminatorMap).build();
}
在生成了discriminator
對應的ResultMapping
對象之后,Mybatis會循環處理所有現有的鑒別器指定字段的值和resultMap的關聯關系,這個處理操作主要是將現有的resultMap
引用由局部改為全局。
完成這些操作之后,mybatis
就會使用這些數據來構建一個Discriminator
對象,負責創建Discriminator
對象的構建器Discriminator.Builder
在實現上基本就是簡單的賦值操作:
public static class Builder {
private Discriminator discriminator = new Discriminator();
public Builder(Configuration configuration, ResultMapping resultMapping, Map<String, String> discriminatorMap) {
discriminator.resultMapping = resultMapping;
discriminator.discriminatorMap = discriminatorMap;
}
public Discriminator build() {
assert discriminator.resultMapping != null;
assert discriminator.discriminatorMap != null;
assert !discriminator.discriminatorMap.isEmpty();
//lock down map
discriminator.discriminatorMap = Collections.unmodifiableMap(discriminator.discriminatorMap);
return discriminator;
}
}
回頭看buildResultMapping方法
ok,處理了關于鑒別器的解析過程之后,我們回過頭來繼續看負責創建ResultMapping
對象的buildResultMapping()
方法:
/**
* 構建ResultMapping實體
*
* @param resultType 返回類型
* @param property 屬性名稱
* @param column 字段名稱
* @param javaType java類型
* @param jdbcType jdbc類型
* @param nestedSelect 嵌套的查詢語句
* @param nestedResultMap 嵌套的resultMap
* @param notNullColumn 非空字段
* @param columnPrefix 列前綴
* @param typeHandler 類型處理器
* @param flags 屬性標記
* @param resultSet 多結果集定義
* @param foreignColumn 父數據列名稱集合
* @param lazy 懶加載標記
*/
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) {
// 推斷返回的java類型
Class<?> javaTypeClass = resolveResultJavaType(resultType, property, javaType);
// 解析類型處理器
TypeHandler<?> typeHandlerInstance = resolveTypeHandler(javaTypeClass, typeHandler);
// 解析混合列,在Mybatis中對于嵌套查詢,我們可以在定義column的時候,使用column= "{prop1=col1,prop2=col2}"
// 這樣的語法來配置多個列名傳入到嵌套查詢語句中的名稱。其中prop1表示嵌套查詢中的參數名稱,col1表示主查詢中列名稱。
List<ResultMapping> composites = parseCompositeColumnName(column);
// 構建ResultMapping
return new ResultMapping.Builder(configuration, property, column, javaTypeClass)
.jdbcType(jdbcType)
.nestedQueryId(applyCurrentNamespace(nestedSelect, true))/*處理嵌套查詢的ID*/
.nestedResultMapId(applyCurrentNamespace(nestedResultMap, true))/*處理嵌套ResultMap的Id*/
.resultSet(resultSet)
.typeHandler(typeHandlerInstance)
.flags(flags == null ? new ArrayList<ResultFlag>() : flags)
.composites(composites) /*混合列*/
.notNullColumns(parseMultipleColumnNames(notNullColumn))
.columnPrefix(columnPrefix)
.foreignColumn(foreignColumn)
.lazy(lazy)
.build();
}
buildResultMapping
方法的參數很多,但是不要慌,因為他的解析真的很簡單!
buildResultMapping
方法首先會借助resolveResultJavaType()
和resolveTypeHandler()
方法解析出當前ResultMapping
對象對應的java
類型以及負責類型轉換的類型轉換處理器實例。
/**
* 解析返回的java類型
*
* @param resultType 返回類型
* @param property 字段名稱
* @param javaType java類型
*/
private Class<?> resolveResultJavaType(Class<?> resultType, String property, Class<?> javaType) {
if (javaType == null && property != null) {
// 沒有javaType根據類的元數據集合獲取javaType
try {
MetaClass metaResultType = MetaClass.forClass(resultType, configuration.getReflectorFactory());
javaType = metaResultType.getSetterType(property);
} catch (Exception e) {
//ignore, following null check statement will deal with the situation
}
}
if (javaType == null) {
javaType = Object.class;
}
return javaType;
}
resolveResultJavaType()
是對前面代碼的一個補充,他可以通過反射操作來獲取ResultMapping
對應的javaType
,如果無法通過反射獲取javaType
,那就默認賦值為Object.class
.
resolveTypeHandler()
方法負責實例化類型轉換處理器,操作很簡單,這里就不在贅述了:
/**
* 解析出指定的類型處理器
*
* @param javaType java類型
* @param typeHandlerType 類型處理器的類型
* @return 類型處理器實例
*/
protected TypeHandler<?> resolveTypeHandler(Class<?> javaType, Class<? extends TypeHandler<?>> typeHandlerType) {
if (typeHandlerType == null) {
return null;
}
// javaType ignored for injected handlers see issue #746 for full detail
TypeHandler<?> handler = typeHandlerRegistry.getMappingTypeHandler(typeHandlerType);
if (handler == null) {
// 創建一個新的類型處理器
// not in registry, create a new one
handler = typeHandlerRegistry.getInstance(javaType, typeHandlerType);
}
return handler;
}
完成這兩個類型的處理之后,mybatis
針對column
屬性值還會執行一次特殊的處理,在介紹association
和collection
元素的配置時,提到column
屬性可以是普通的列名稱定義,比如column="id"
,也可以是一個復合的屬性描述,比如:column="{prop1=col1,prop2=col2}"
.
所以針對復合屬性描述,mybatis
會通過parseCompositeColumnName()
方法將其解析成一組ResultMapping
定義:
private List<ResultMapping> parseCompositeColumnName(String columnName) {
List<ResultMapping> composites = new ArrayList<>();
if (columnName != null && (columnName.indexOf('=') > -1 || columnName.indexOf(',') > -1)) {
// 以 【{}=,】 作為分隔符處理內容
StringTokenizer parser = new StringTokenizer(columnName, "{}=, ", false);
while (parser.hasMoreTokens()) {
// 獲取屬性名稱
String property = parser.nextToken();
// 獲取列名稱
String column = parser.nextToken();
ResultMapping complexResultMapping = new ResultMapping.Builder(
configuration /*Mybatis配置*/
, property/*屬性名稱*/
, column/*列名稱*/
, configuration.getTypeHandlerRegistry().getUnknownTypeHandler()/*Mybatis默認的未知類型的轉換處理器*/
).build();
composites.add(complexResultMapping);
}
}
return composites;
}
這一組ResultMapping
定義有別于常規意義上的ResultMapping
,它配置的是嵌套查詢中,主查詢結果對象中屬性名稱和子查詢語句的參數關系.
最后buildResultMapping()
方法就會通過前面處理好的屬性完成一個ResultMapping
對象的創建工作:
// 構建ResultMapping
return new ResultMapping.Builder(configuration, property, column, javaTypeClass)
.jdbcType(jdbcType)
.nestedQueryId(applyCurrentNamespace(nestedSelect, true))/*處理嵌套查詢的ID*/
.nestedResultMapId(applyCurrentNamespace(nestedResultMap, true))/*處理嵌套ResultMap的Id*/
.resultSet(resultSet)
.typeHandler(typeHandlerInstance)
.flags(flags == null ? new ArrayList<ResultFlag>() : flags)
.composites(composites) /*混合列*/
.notNullColumns(parseMultipleColumnNames(notNullColumn))
.columnPrefix(columnPrefix)
.foreignColumn(foreignColumn)
.lazy(lazy)
.build();
在上面的方法調用中,針對引用的嵌套查詢語句和嵌套映射,還提前做了一個局部ID
轉全局ID
的操作.
在buildResultMapping()
和parseCompositeColumnName()
兩個方法中,實際創建ResultMapping
對象的工作都是由ResultMapping
的構建器ResultMapping.Builder
來完成的.
ResultMapping的創建工作
都講到這里了,實在是避不開ResultMapping
對象了,但是ResultMapping
對象雖然看起來屬性很多,可這些屬性基本上咱們都做了一定的了解了,所以這個代碼我就隨手一貼,你就隨手一看,咱也不大費周折的去看每一個屬性了:
public class ResultMapping {
/**
* 配置
*/
private Configuration configuration;
/**
* 屬性名稱
*/
private String property;
/**
* 對應的列名稱
*/
private String column;
/**
* java類型
*/
private Class<?> javaType;
/**
* jdbc類型
*/
private JdbcType jdbcType;
/**
* 類型處理器
*/
private TypeHandler<?> typeHandler;
/**
* 內部嵌套的或引用的ResultMap
*/
private String nestedResultMapId;
/**
* 內部嵌套的或引用的查詢語句
*/
private String nestedQueryId;
/**
* 非空字段集合
*/
private Set<String> notNullColumns;
/**
* 列名前綴
*/
private String columnPrefix;
/**
* 返回類型標記
* 構造參數,JDBC主鍵
*/
private List<ResultFlag> flags;
/**
* resultMaps,嵌套的resultMap定義,是通過嵌套語句的column字段中以column={a=c,b=d}的方式定義出來的集合
*/
private List<ResultMapping> composites;
/**
* resultSet
*/
private String resultSet;
/**
* 外鍵
*/
private String foreignColumn;
/**
* 懶加載標記
*/
private boolean lazy;
ResultMapping() {
}
// ... 省略 ...
}
ResultMapping.Builder
的工作流程并不復雜,他提供的方法除了構造方法和build()
方法之外基本都是簡單的屬性賦值操作.
構造方法的實現也不復雜,在重載形式為Builder(Configuration configuration, String property)
的構造方法中,完成了部分屬性的初始化操作:
public Builder(Configuration configuration, String property) {
resultMapping.configuration = configuration;
resultMapping.property = property;
resultMapping.flags = new ArrayList<>();
resultMapping.composites = new ArrayList<>();
resultMapping.lazy = configuration.isLazyLoadingEnabled();
}
這里面最特殊的一行應該就是lazy
屬性的初始化使用了全局懶加載配置.
build()
方法的實現主要還是做了一些對屬性二次處理和校驗的工作:
public ResultMapping build() {
// lock down collections
resultMapping.flags = Collections.unmodifiableList(resultMapping.flags);
resultMapping.composites = Collections.unmodifiableList(resultMapping.composites);
resolveTypeHandler();
validate();
return resultMapping;
}
比如將ResultMapping
對象中一些集合類型的屬性置為不可變:
resultMapping.flags = Collections.unmodifiableList(resultMapping.flags);
resultMapping.composites = Collections.unmodifiableList(resultMapping.composites);
在沒有指定類型轉換處理器的前提下,根據javaType
屬性推斷出可用的類型轉換處理器實例:
private void resolveTypeHandler() {
if (resultMapping.typeHandler == null && resultMapping.javaType != null) {
Configuration configuration = resultMapping.configuration;
TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
resultMapping.typeHandler = typeHandlerRegistry.getTypeHandler(resultMapping.javaType, resultMapping.jdbcType);
}
}
以及對當前ResultMapping
對象的完整性進行校驗:
private void validate() {
// 在一個ResultMapping定義中不能同時引用nestedQueryId和nestedResultMapId
// Issue #697: cannot define both nestedQueryId and nestedResultMapId
if (resultMapping.nestedQueryId != null && resultMapping.nestedResultMapId != null) {
throw new IllegalStateException("Cannot define both nestedQueryId and nestedResultMapId in property " + resultMapping.property);
}
// 沒有類型處理程序就不應該有映射
// Issue #5: there should be no mappings without typehandler
if (resultMapping.nestedQueryId == null && resultMapping.nestedResultMapId == null && resultMapping.typeHandler == null) {
throw new IllegalStateException("No typehandler found for property " + resultMapping.property);
}
// column 僅在嵌套的結果圖中可選,但在其余部分中不可選
// Issue #4 and GH #39: column is optional only in nested resultmaps but not in the rest
if (resultMapping.nestedResultMapId == null && resultMapping.column == null && resultMapping.composites.isEmpty()) {
throw new IllegalStateException("Mapping is missing column attribute for property " + resultMapping.property);
}
// 屬性中應該有相同數量的列和foreignColumns
if (resultMapping.getResultSet() != null) {
int numColumns = 0;
if (resultMapping.column != null) {
numColumns = resultMapping.column.split(",").length;
}
int numForeignColumns = 0;
if (resultMapping.foreignColumn != null) {
numForeignColumns = resultMapping.foreignColumn.split(",").length;
}
if (numColumns != numForeignColumns) {
throw new IllegalStateException("There should be the same number of columns and foreignColumns in property " + resultMapping.property);
}
}
}
首先,一個ResultMapping
對象是不能同時指定嵌套查詢和嵌套結果映射的.
// 在一個ResultMapping定義中不能同時引用nestedQueryId和nestedResultMapId
// Issue #697: cannot define both nestedQueryId and nestedResultMapId
if (resultMapping.nestedQueryId != null && resultMapping.nestedResultMapId != null) {
throw new IllegalStateException("Cannot define both nestedQueryId and nestedResultMapId in property " + resultMapping.property);
}
其次,一個ResultMapping
對象如果沒有指定嵌套查詢,也沒有指定嵌套結果映射,那么,他就應該有一個可用的類型轉換處理器,不然,是沒辦法完成將數據庫數據轉換為對象的操作的.
// 沒有類型處理程序就不應該有映射
// Issue #5: there should be no mappings without typehandler
if (resultMapping.nestedQueryId == null && resultMapping.nestedResultMapId == null && resultMapping.typeHandler == null) {
throw new IllegalStateException("No typehandler found for property " + resultMapping.property);
}
同時,如果一個ResultMapping
對象沒有指定嵌套結果映射,那么就意味著這個ResultMapping
對象必須指定了column
屬性,否則,他無法完成屬性的映射或者執行子查詢.
// column 僅在嵌套的結果圖中可選,但在其余部分中不可選
// Issue #4 and GH #39: column is optional only in nested resultmaps but not in the rest
if (resultMapping.nestedResultMapId == null && resultMapping.column == null && resultMapping.composites.isEmpty()) {
throw new IllegalStateException("Mapping is missing column attribute for property " + resultMapping.property);
}
最后,因為在多結果集模式
下,column
屬性將會配合著foreignColumn
屬性一起使用,且foreignColumn
屬性和column
屬性之間是順序關聯的.
所以,foreignColumn
屬性和column
屬性所配置列的數量應該是一致的.
// 屬性中應該有相同數量的列和foreignColumns
if (resultMapping.getResultSet() != null) {
int numColumns = 0;
if (resultMapping.column != null) {
numColumns = resultMapping.column.split(",").length;
}
int numForeignColumns = 0;
if (resultMapping.foreignColumn != null) {
numForeignColumns = resultMapping.foreignColumn.split(",").length;
}
if (numColumns != numForeignColumns) {
throw new IllegalStateException("There should be the same number of columns and foreignColumns in property " + resultMapping.property);
}
}
著手準備構建ResultMap對象
那么,到這里,我們就完成一個ResultMapping
對象的創建工作,接下來,我們回過頭來去看,在得到ResultMapping
集合之后,mybatis
是如何創建ResultMap
對象的.
現在我們回到resultMapElement()
方法中,此時我們已經完成了resultMap
屬性及其子元素的解析工作.
接下來,mybatis
就會利用我們獲取到這些數據,構建一個ResultMapResolver
對象,來完成一個ResultMap
對象的創建工作.
// 構建ResultMap解析器
ResultMapResolver resultMapResolver = new ResultMapResolver(
builderAssistant
, id /*resultMap的ID*/
, typeClass /*返回類型*/
, extend /*繼承的ResultMap*/
, discriminator /*鑒別器*/
, resultMappings /*內部的ResultMapping集合*/
, autoMapping /*自動映射*/
);
try {
// 解析ResultMap
return resultMapResolver.resolve();
} catch (IncompleteElementException e) {
// 解析ResultMap發生異常,將獎蓋ResultMap放入未完成解析的ResultMap集合.
configuration.addIncompleteResultMap(resultMapResolver);
throw e;
}
如果在構建ResultMap
對象的過程中觸發了IncompleteElementException
異常,整個ResultMapResolver
對象都會被存入到Configuration
對象的incompleteResultMaps
集合中,等待下次重試.
這個重試實現和緩存引用的處理邏輯基本一致,因為可能會出現跨mapper
文件引用resultMap
配置的場景,所以提供了該重試機制.
回頭看XMLMapperBuilder
的parse()
方法,每解析一次mapper
文件,都會嘗試重新解析出現解析異常的ResultMap
對象:
ResultMapResolver
對象和CacheRefResolver
對象很像,它緩存了創建一個ResultMap
對象所需的所有數據:
/**
* ResultMap解析器
*
* @author Eduardo Macarron
*/
public class ResultMapResolver {
/**
* Mapper構造助手
*/
private final MapperBuilderAssistant assistant;
/**
* resultMap的唯一標志
*/
private final String id;
/**
* ResultMap的返回類型
*/
private final Class<?> type;
/**
* 擴展(繼承)的ResultMap集合
*/
private final String extend;
/**
* 鑒別器
*/
private final Discriminator discriminator;
/**
* 所有的resultMap子字段集合
*/
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;
}
/**
* 解析并生成ResultMap
*
* @return resultMap
*/
public ResultMap resolve() {
return assistant.addResultMap(
this.id
, this.type
, this.extend
, this.discriminator
, this.resultMappings
, this.autoMapping
);
}
}
并在resolve()
方法中將ResultMap
對象的創建工作委托給MapperBuilderAssistant
對象的addResultMap()
方法來完成.
MapperBuilderAssistant
對象的addResultMap()
方法
addResultMap()
方法的作用是創建一個ResultMap
對象,并注冊到Configuration
對象的ResultMap
注冊表中,這個方法的實現代碼很長,但是也不復雜.
在實現上,他做的工作主要就是解析ResultMap
對象的繼承關系,并合并具有繼承關系的兩個ResultMap
對象的配置到子ResultMap
對象中:
/**
* 添加(注冊)一個ResultMap集合
*
* @param id ResultMap唯一標志
* @param type 返回類型
* @param extend 繼承的ResultMap
* @param discriminator 鑒別器
* @param resultMappings 現有的ResultMapping集合
* @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)) {
// 不存在引用(繼承)的ResultMap,標記為incomplete,待第二次處理
throw new IncompleteElementException("Could not find a parent resultmap with id '" + extend + "'");
}
// 獲取被引入(繼承)的ResultMaps
ResultMap resultMap = configuration.getResultMap(extend);
// 獲取引入的resultMap的所有子節點配置
List<ResultMapping> extendedResultMappings = new ArrayList<>(resultMap.getResultMappings());
// 本地覆蓋繼承
extendedResultMappings.removeAll(resultMappings);
// Remove parent constructor if this resultMap declares a constructor.
// 當前resultMap是否聲明了構造函數
boolean declaresConstructor = false;
for (ResultMapping resultMapping : resultMappings) {
if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) {
// 當前resultMap聲明了構造函數
declaresConstructor = true;
break;
}
}
if (declaresConstructor) {
// 如果已經聲明了構造函數,準備移除父resultMap的構造函數
Iterator<ResultMapping> extendedResultMappingsIter = extendedResultMappings.iterator();
while (extendedResultMappingsIter.hasNext()) {
if (extendedResultMappingsIter.next().getFlags().contains(ResultFlag.CONSTRUCTOR)) {
// 移除被繼承的resultMap的構造函數
extendedResultMappingsIter.remove();
}
}
}
//合并自身的resultMap以及繼承的resultMap的內容,獲得最終的resultMap,這也意味著在啟動時就創建了完整的resultMap,
// 這樣在運行時就不需要去檢查繼承的映射和構造器,有利于性能提升。
resultMappings.addAll(extendedResultMappings);
}
// 構造ResultMap
ResultMap resultMap = new ResultMap
.Builder(
configuration
, id
, type
, resultMappings
, autoMapping
)
.discriminator(discriminator)
.build();
// 注冊resultMap
configuration.addResultMap(resultMap);
return resultMap;
}
在實現上,首先addResultMap()
方法會將當前待處理的ResultMap
和被繼承的ResultMap
的id
通過applyCurrentNamespace()
方法轉換為全局引用標志,便于統一處理.
// 獲取命名空間標志
id = applyCurrentNamespace(id, false);
// 繼承的命名空間
extend = applyCurrentNamespace(extend, true);
然后,如果當前ResultMap
對象存在繼承的ResultMap
對象,就將父ResultMap
對象中的配置合并到當前ResultMap
對象中.
在合并過程中,首先會校驗被繼承的父ResultMap
對象是否已經配置到了Configuration
中,如果沒有的話,將會拋出一個IncompleteElementException
,中斷本次解析,等待重試.
// 獲取被引入(繼承)的ResultMaps
ResultMap resultMap = configuration.getResultMap(extend);
// 獲取引入的resultMap的所有子節點配置
List<ResultMapping> extendedResultMappings = new ArrayList<>(resultMap.getResultMappings());
// 本地覆蓋繼承
extendedResultMappings.removeAll(resultMappings);
在拿到父ResultMap
對象之后,addResultMap()
方法會移除所有在當前ResultMap
對象中定義的相同配置,
因為ResultMapping
對象重寫了equals()
方法,因此具有相同屬性名稱(property
)的ResultMapping
對象會被認為是相同的:
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
ResultMapping that = (ResultMapping) o;
if (property == null || !property.equals(that.property)) {
return false;
}
return true;
}
如果父ResultMap
對象還配置了構造參數,那么所有構造參數對應的配置都會被移除:
// 當前resultMap是否聲明了構造函數
boolean declaresConstructor = false;
for (ResultMapping resultMapping : resultMappings) {
if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) {
// 當前resultMap聲明了構造函數
declaresConstructor = true;
break;
}
}
if (declaresConstructor) {
// 如果已經聲明了構造函數,準備移除父resultMap的構造函數
Iterator<ResultMapping> extendedResultMappingsIter = extendedResultMappings.iterator();
while (extendedResultMappingsIter.hasNext()) {
if (extendedResultMappingsIter.next().getFlags().contains(ResultFlag.CONSTRUCTOR)) {
// 移除被繼承的resultMap的構造函數
extendedResultMappingsIter.remove();
}
}
}
最后將當前ResultMap
對象的配置和父ResultMap
對象的配置合二為一:
//合并自身的resultMap以及繼承的resultMap的內容,獲得最終的resultMap,這也意味著在啟動時就創建了完整的resultMap,
// 這樣在運行時就不需要去檢查繼承的映射和構造器,有利于性能提升。
resultMappings.addAll(extendedResultMappings);
這樣就完成了ResultMap
對象繼承關系的處理,然后就是通過ResultMap
的構建器來完成創建ResultMap
對象的工作,并將得到的ResultMap
對象注冊到Configuration
中.
ResultMap對象
無論是創建ResultMap
對象還是注冊ResultMap
對象,這兩個操作都涉及到了一些額外的操作,為了能夠更好的理解ResultMap
對象的創建和注冊行為,我們先簡單了解一下ResultMap
對象.
public class ResultMap {
/**
* Mybatis配置對象
*/
private Configuration configuration;
/**
* resultMap的唯一標志
*/
private String id;
/**
* resultMap的返回類型
*/
private Class<?> type;
/**
* resultMap下的所有節點
*/
private List<ResultMapping> resultMappings;
/**
* resultMap下的所有id節點
*/
private List<ResultMapping> idResultMappings;
/**
* resultMap下的所有構造器節點
*/
private List<ResultMapping> constructorResultMappings;
/**
* resultMap下的所有普通屬性節點
*/
private List<ResultMapping> propertyResultMappings;
/**
* 映射處理的數據列名集合
*/
private Set<String> mappedColumns;
/**
* 映射的所有javaBean屬性名,包括ID,構造器,普通屬性。
*/
private Set<String> mappedProperties;
/**
* 鑒別器
*/
private Discriminator discriminator;
/**
* 是否持有嵌套的resultMap,比如association或者collection,
* 如果它包含discriminator,那么discriminator所持有的ResultMap對象的hasNestedResultMaps屬性會影響該屬性.
*/
private boolean hasNestedResultMaps;
/**
* 是否有嵌套的查詢,比如select屬性
*/
private boolean hasNestedQueries;
/**
* 自動映射,該屬性會覆蓋全局屬性
*/
private Boolean autoMapping;
private ResultMap() {
}
// ...省略...
}
在上面的代碼中,針對ResultMap
的每個屬性都給出了注釋,如果有不了解的,在本文后面的內容中,還會有更詳細的介紹.
創建ResultMap
對象
簡單了解了ResultMap
對象的定義之后,我們回頭繼續看在MapperBuilderAssistant
的addResultMap()
方法創建ResultMap
對象的操作:
// 構造ResultMap
ResultMap resultMap = new ResultMap
.Builder(
configuration
, id
, type
, resultMappings
, autoMapping
)
.discriminator(discriminator)
.build();
ResultMap.Builder
是負責創建ResultMap
對象的構建器,在上面的方法調用鏈中,除了build()
方法之外,所有的方法實現均是簡單的賦值操作.
在實現上與眾不同的build()
方法責任重大,他完成了ResultMap
對象部分數據的初始化和校驗工作.
build()
方法的實現相對比較長,涉及到的屬性也比較多,我們先總覽一下代碼,然后我們再細看該方法的實現:
public ResultMap build() {
if (resultMap.id == null) {
throw new IllegalArgumentException("ResultMaps must have an id");
}
resultMap.mappedColumns = new HashSet<>();
resultMap.mappedProperties = new HashSet<>();
resultMap.idResultMappings = new ArrayList<>();
resultMap.constructorResultMappings = new ArrayList<>();
resultMap.propertyResultMappings = new ArrayList<>();
final List<String> constructorArgNames = new ArrayList<>();
for (ResultMapping resultMapping : resultMap.resultMappings) {
// 初始化是否含有嵌套查詢語句
resultMap.hasNestedQueries = resultMap.hasNestedQueries || resultMapping.getNestedQueryId() != null;
// 初始化是否含有嵌套resultMap
resultMap.hasNestedResultMaps = resultMap.hasNestedResultMaps || (resultMapping.getNestedResultMapId() != null && resultMapping.getResultSet() == null);
// 獲取當前列,包括復合列,
// 復合列是在org.apache.ibatis.builder.MapperBuilderAssistant.parseCompositeColumnName(String)中解析的。
// 所有的數據庫列都被按順序添加到resultMap.mappedColumns中
final String column = resultMapping.getColumn();
if (column != null) {
// 添加映射的列名稱
resultMap.mappedColumns.add(column.toUpperCase(Locale.ENGLISH));
} else if (resultMapping.isCompositeResult()) {
// 當前是符合列
for (ResultMapping compositeResultMapping : resultMapping.getComposites()) {
// 獲取復合列的列名稱
final String compositeColumn = compositeResultMapping.getColumn();
if (compositeColumn != null) {
// 添加映射的列名稱
resultMap.mappedColumns.add(compositeColumn.toUpperCase(Locale.ENGLISH));
}
}
}
// 獲取javaBean的字段類型
final String property = resultMapping.getProperty();
if (property != null) {
// 添加到映射的屬性集合中
resultMap.mappedProperties.add(property);
}
// 如果本元素具有CONSTRUCTOR標記,則添加到構造函數參數列表,否則添加到普通屬性映射列表resultMap.propertyResultMappings
if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) {
// 處理構造函數,注冊到當前的構造函數映射集合中
resultMap.constructorResultMappings.add(resultMapping);
if (resultMapping.getProperty() != null) {
// 添加到構造函數集合內
constructorArgNames.add(resultMapping.getProperty());
}
} else {
// 不是構造函數,直接添加到普通屬性集合內
resultMap.propertyResultMappings.add(resultMapping);
}
// 如果當前元素有ID標記,則添加到ID映射列表內
if (resultMapping.getFlags().contains(ResultFlag.ID)) {
// 如果是ID標志,添加到ID映射集合中
resultMap.idResultMappings.add(resultMapping);
}
}
// 循環結束
// 如果當前resultMap沒有聲明ID屬性,就把所有的屬性都作為ID屬性
if (resultMap.idResultMappings.isEmpty()) {
resultMap.idResultMappings.addAll(resultMap.resultMappings);
}
// 據聲明的構造器參數名和類型,反射聲明的類,
// 檢查其中是否包含對應參數名和類型的構造器,
// 如果不存在匹配的構造器,就拋出運行時異常,這是為了確保運行時不會出現異常
if (!constructorArgNames.isEmpty()) {
// 獲取構造參數中的名稱集合
final List<String> actualArgNames = argNamesOfMatchingConstructor(constructorArgNames);
if (actualArgNames == null) {
throw new BuilderException("Error in result map '" + resultMap.id
+ "'. Failed to find a constructor in '"
+ resultMap.getType().getName() + "' by arg names " + constructorArgNames
+ ". There might be more info in debug log.");
}
// 給resultMap的構造器參數排序
Collections.sort(resultMap.constructorResultMappings, (o1, o2) -> {
int paramIdx1 = actualArgNames.indexOf(o1.getProperty());
int paramIdx2 = actualArgNames.indexOf(o2.getProperty());
return paramIdx1 - paramIdx2;
});
}
// lock down collections
// 為了避免resultMap的內部結構發生變更, 克隆一個不可修改的集合提供給用戶
resultMap.resultMappings = Collections.unmodifiableList(resultMap.resultMappings);
resultMap.idResultMappings = Collections.unmodifiableList(resultMap.idResultMappings);
resultMap.constructorResultMappings = Collections.unmodifiableList(resultMap.constructorResultMappings);
resultMap.propertyResultMappings = Collections.unmodifiableList(resultMap.propertyResultMappings);
resultMap.mappedColumns = Collections.unmodifiableSet(resultMap.mappedColumns);
return resultMap;
}
首先build()
方法對要創建的ResultMap
對象的id
屬性做了最基礎的校驗,因為id
屬性是mybatis
操作ResultMap
對象時的唯一憑據.
if (resultMap.id == null) {
throw new IllegalArgumentException("ResultMaps must have an id");
}
之后build()
方法會循環處理所有的ResultMappings
配置,并根據ResultMappings
的配置來完成部分核心屬性的初始化工作.
比如,初始化當前ResultMap
對象中負責描述是否存在嵌套查詢語句和嵌套結果映射的hasNestedQueries
和hasNestedResultMaps
屬性:
// 初始化是否含有嵌套查詢語句
resultMap.hasNestedQueries = resultMap.hasNestedQueries || resultMapping.getNestedQueryId() != null;
// 初始化是否含有嵌套resultMap
resultMap.hasNestedResultMaps = resultMap.hasNestedResultMaps || (resultMapping.getNestedResultMapId() != null && resultMapping.getResultSet() == null);
以及初始化負責維護當前ResultMap
對象能夠處理哪些數據列的集合mappedColumns
:
final String column = resultMapping.getColumn();
if (column != null) {
// 添加映射的列名稱
resultMap.mappedColumns.add(column.toUpperCase(Locale.ENGLISH));
} else if (resultMapping.isCompositeResult()) {
// 當前是符合列
for (ResultMapping compositeResultMapping : resultMapping.getComposites()) {
// 獲取復合列的列名稱
final String compositeColumn = compositeResultMapping.getColumn();
if (compositeColumn != null) {
// 添加映射的列名稱
resultMap.mappedColumns.add(compositeColumn.toUpperCase(Locale.ENGLISH));
}
}
}
mappedColumns
的取值有兩種,一種是用戶直接配置的簡單列名稱,一種用戶配置的復合的屬性描述中的數據列名稱.
還有初始化負責維護當前ResultMap
對象能夠處理哪些屬性的集合mappedProperties
:
// 獲取javaBean的字段類型
final String property = resultMapping.getProperty();
if (property != null) {
// 添加到映射的屬性集合中
resultMap.mappedProperties.add(property);
}
最后,根據每個ResultMapping
對象的標記,是構造參數配置的放入到維護構造參數映射關系的constructorResultMappings
集合中,不是構造參數的放入到維護普通屬性映射關系的propertyResultMappings
集合中.
// 如果本元素具有CONSTRUCTOR標記,則添加到構造函數參數列表,否則添加到普通屬性映射列表resultMap.propertyResultMappings
if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) {
// 處理構造函數,注冊到當前的構造函數映射集合中
resultMap.constructorResultMappings.add(resultMapping);
if (resultMapping.getProperty() != null) {
// 添加到構造函數集合內
constructorArgNames.add(resultMapping.getProperty());
}
} else {
// 不是構造函數,直接添加到普通屬性集合內
resultMap.propertyResultMappings.add(resultMapping);
}
而且針對構造參數配置,如果指定了構造參數的形參名稱,還會將該形參名稱放入到一個名為constructorArgNames
的集合中,constructorArgNames
是個局部變量,用于構造方法的校驗工作.
如果ResultMapping
對象還持有ResultFlag.ID
標記,那么這個ResultMapping
對象還會被放進負責維護id屬性映射關系的idResultMappings
集合中.
// 如果當前元素有ID標記,則添加到ID映射列表內
if (resultMapping.getFlags().contains(ResultFlag.ID)) {
// 如果是ID標志,添加到ID映射集合中
resultMap.idResultMappings.add(resultMapping);
}
在循環處理完所有的ResultMappings
配置之后,ResultMap
對象屬性的初始化工作基本就完成了,但是針對idResultMappings
集合,還會有一步額外的操作.
不知道你是否還記得我們在介紹id
元素的時候,因為濫用id
元素造成的數據丟失問題?
在那篇文章我們做了一個總結:
被
id
元素標識的屬性將會作為對象的標識符,該標識符會在比較對象實例的時候被使用.
但是沒有說,如果沒有配置id
元素,如何比較對象實例.
針對沒有配置id
元素的場景,build()
方法會把當前ResultMap
對象的所有ResultMapping
配置放入到idResultMappings
集合中,用來作為唯一標識:
// 如果當前resultMap沒有聲明ID屬性,就把所有的屬性都作為ID屬性
if (resultMap.idResultMappings.isEmpty()) {
resultMap.idResultMappings.addAll(resultMap.resultMappings);
}
到這里,ResultMap
對象屬性的初始化工作才算完成,接下來就是構造方法的校驗工作了,如果用戶配置構造參數的時候指定了構造參數的形參名稱,那么build()
方法就會根據形參名稱去尋找相應的構造方法,并進行基礎的校驗工作:
// 據聲明的構造器參數名和類型,反射聲明的類,
// 檢查其中是否包含對應參數名和類型的構造器,
// 如果不存在匹配的構造器,就拋出運行時異常,這是為了確保運行時不會出現異常
if (!constructorArgNames.isEmpty()) {
// 獲取構造參數中的名稱集合
final List<String> actualArgNames = argNamesOfMatchingConstructor(constructorArgNames);
if (actualArgNames == null) {
throw new BuilderException("Error in result map '" + resultMap.id
+ "'. Failed to find a constructor in '"
+ resultMap.getType().getName() + "' by arg names " + constructorArgNames
+ ". There might be more info in debug log.");
}
// 給resultMap的構造器參數排序
Collections.sort(resultMap.constructorResultMappings, (o1, o2) -> {
int paramIdx1 = actualArgNames.indexOf(o1.getProperty());
int paramIdx2 = actualArgNames.indexOf(o2.getProperty());
return paramIdx1 - paramIdx2;
});
}
在Mybatis源碼之美:3.5.5.配置構造方法的constructor元素一文中,我們講從版本3.4.3
開始,mybatis
開始支持根據參數名稱匹配所對應的構造方法,這里就是對這一特性的處理和校驗.
argNamesOfMatchingConstructor()
方法負責根據現有的constructorArgNames
形參名稱集合,來尋找相匹配的第一個構造方法,并返回匹配構造方法的有序形參名稱集合.
因為該方法的解析涉及到一些額外的操作,所以我們待會再補充該方法的實現細節,現在讓我們先完成build()
方法的后續操作.
當build()
方法得到有序形參名稱集合之后,會利用該集合對現有的constructorResultMappings
集合進行排序,這樣constructorResultMappings
集合中存放的配置就和實際的構造方法順序對應上了.
最后,build()
方法借助于Collections
的unmodifiableList()
方法將上面配置的這些集合轉換為不可變更的集合,至此就完成了一個ResultMap
對象的創建工作了.
// lock down collections
// 為了避免resultMap的內部結構發生變更, 克隆一個不可修改的集合提供給用戶
resultMap.resultMappings = Collections.unmodifiableList(resultMap.resultMappings);
resultMap.idResultMappings = Collections.unmodifiableList(resultMap.idResultMappings);
resultMap.constructorResultMappings = Collections.unmodifiableList(resultMap.constructorResultMappings);
resultMap.propertyResultMappings = Collections.unmodifiableList(resultMap.propertyResultMappings);
resultMap.mappedColumns = Collections.unmodifiableSet(resultMap.mappedColumns);
return resultMap;
argNamesOfMatchingConstructor()
方法
現在我們可以回頭看一下argNamesOfMatchingConstructor()
方法的實現了.
這個方法的實現邏輯是這樣的,先獲取返回對象類型的所有構造方法,然后篩選出構造參數數量和現有constructorArgNames
中維護的形參名稱數量一致的構造方法.
private List<String> argNamesOfMatchingConstructor(List<String> constructorArgNames) {
// 獲取resultMap對應的javabean的構造函數集合
Constructor<?>[] constructors = resultMap.type.getDeclaredConstructors();
for (Constructor<?> constructor : constructors) {
// 獲取當前構造函數的入參列表
Class<?>[] paramTypes = constructor.getParameterTypes();
// 處理參數列表和當前入參數量一致的構造函數
if (constructorArgNames.size() == paramTypes.length) {
// 獲取構造參數的入參名稱集合(有序)
List<String> paramNames = getArgNames(constructor);
if (constructorArgNames.containsAll(paramNames) /*參數名稱一致*/
&& argTypesMatch(
constructorArgNames
, paramTypes /*真正的參數類型集合*/
, paramNames/*真正的參數名稱集合*/
)/*類型是否一致*/
) {
return paramNames;
}
}
}
return null;
}
然后用匹配到的構造方法的參數類型和形參名稱,去一一對應用戶配置的構造參數名稱和類型,如果能夠匹配,則表示該構造方法是一個有效的構造方法,返回該構造方法的形參名稱集合即可.
// 獲取構造參數的入參名稱集合(有序)
List<String> paramNames = getArgNames(constructor);
if (constructorArgNames.containsAll(paramNames) /*參數名稱一致*/
&& argTypesMatch(
constructorArgNames
, paramTypes /*真正的參數類型集合*/
, paramNames/*真正的參數名稱集合*/
)/*類型是否一致*/
) {
return paramNames;
}
負責獲取構造方法形參名稱的getArgNames()
方法的實現別有洞天:
private List<String> getArgNames(Constructor<?> constructor) {
List<String> paramNames = new ArrayList<>();
List<String> actualParamNames = null;
// 獲取參數列表中的注解集合,每一個構造參數都對應一個Annotation數組。
final Annotation[][] paramAnnotations = constructor.getParameterAnnotations();
/*
* 構造參數的數量
*/
int paramCount = paramAnnotations.length;
for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
// 處理每一個構造參數
String name = null;
for (Annotation annotation : paramAnnotations[paramIndex]) {
// 尋找當前構造參數上的Param注解
if (annotation instanceof Param) {
name = ((Param) annotation).value();
break;
}
}
if (name == null && resultMap.configuration.isUseActualParamName()) {
// 如果沒有添加Param注解,同時還開啟了使用真實參數的功能的話,則使用真實參數名稱
if (actualParamNames == null) {
//獲取構造參數的所有入參的參數名稱集合
actualParamNames = ParamNameUtil.getParamNames(constructor);
}
if (actualParamNames.size() > paramIndex) {
name = actualParamNames.get(paramIndex);
}
}
// 添加參數名稱,如果沒有找到名稱的話,則使用arg+參數索引
paramNames.add(name != null ? name : "arg" + paramIndex);
}
return paramNames;
}
在所有配置形參名稱的方案中,通過@Param
注解配置的屬性名優先級最高,開啟了useActualParamName
特性下的真實形參名稱略低,保底的形參名稱則是argN
,其中N
表示形參索引.
負責獲取真實形參名稱的ParamNameUtil
的getParamNames()
方法實現比較簡單,經過一次跳轉,該方法最終是借助于反射機制來完成的形參名稱獲取操作.
/**
* 獲取指定方法的所有入參的參數名稱集合
*
* @param executable 方法(Executable表示普通方法和構造方法的通用父類)
* @return 指定方法的所有入參的參數名稱集合
*/
private static List<String> getParameterNames(Executable executable) {
final List<String> names = new ArrayList<>();
// 獲取方法所有入參
final Parameter[] params = executable.getParameters();
for (Parameter param : params) {
// 添加參數名稱
names.add(param.getName());
}
// 返回
return names;
}
在argNamesOfMatchingConstructor
方法中,除了getArgNames()
方法之外,還有一個argTypesMatch()
方法,該方法用來校驗指定構造方法的形參名稱和類型,能否和用戶配置的形參和類型對應上:
/**
* 匹配構造參數的類型
*
* @param constructorArgNames 構造函數的參數名稱集合
* @param paramTypes 參數類型
* @param paramNames 參數名稱
* @return 是否匹配
*/
private boolean argTypesMatch(final List<String> constructorArgNames,
Class<?>[] paramTypes, List<String> paramNames) {
for (int i = 0; i < constructorArgNames.size(); i++) {
// 處理每一個構造參數
// 獲取參數類型
Class<?> actualType = paramTypes[paramNames.indexOf(constructorArgNames.get(i))];
// 獲取構造函數的參數類型
Class<?> specifiedType = resultMap.constructorResultMappings.get(i).getJavaType();
if (!actualType.equals(specifiedType)) {
// 判斷二者是否一致
if (log.isDebugEnabled()) {
log.debug("While building result map '" + resultMap.id
+ "', found a constructor with arg names " + constructorArgNames
+ ", but the type of '" + constructorArgNames.get(i)
+ "' did not match. Specified: [" + specifiedType.getName() + "] Declared: ["
+ actualType.getName() + "]");
}
return false;
}
}
return true;
}
至此,構建ResultMap
對象涉及到的方法就已經了解完畢了,現在我們回過頭去看,向Configuration
對象注冊ResultMap
對象時又執行了那些額外操作?
注冊ResultMap對象
Configuration
對象的addResultMap()
方法用于注冊ResultMap
對象,該方法的實現除了會將ResultMap
對象存入到resultMaps
集合中,還會執行以下額外的操作:
public void addResultMap(ResultMap rm) {
resultMaps.put(rm.getId(), rm);
/*檢查當前resultMap內的鑒別器是否嵌套ResultMap*/
checkLocallyForDiscriminatedNestedResultMaps(rm);
/*檢查所有的ResultMap內的鑒別器是否嵌套ResultMap*/
checkGloballyForDiscriminatedNestedResultMaps(rm);
}
其中checkLocallyForDiscriminatedNestedResultMaps()
方法用于檢測當前ResultMap
對象中是否配置了Discriminator
,以及Discriminator
中是否包含嵌套結果映射.
// Slow but a one time cost. A better solution is welcome.
protected void checkLocallyForDiscriminatedNestedResultMaps(ResultMap rm) {
if (!rm.hasNestedResultMaps() && rm.getDiscriminator() != null) {
// 當前ResultMap不含有嵌套的ResultMap,同時含有鑒別器
for (Map.Entry<String, String> entry : rm.getDiscriminator().getDiscriminatorMap().entrySet()) {
// 獲取鑒別器對應的ResultMap
String discriminatedResultMapName = entry.getValue();
// 已經加載了鑒別器對應的ResultMap
if (hasResultMap(discriminatedResultMapName)) {
// 獲取鑒別器對應的ResultMap
ResultMap discriminatedResultMap = resultMaps.get(discriminatedResultMapName);
if (discriminatedResultMap.hasNestedResultMaps()) {
// 更新ResultMap的hasNestedResultMaps字段為true.
rm.forceNestedResultMaps();
break;
}
}
}
}
}
如果當前ResultMap
對象中配置了Discriminator
,且Discriminator
中包含嵌套結果映射,那么就意味著當前ResultMap
對象也包含了嵌套結果映射.
畢竟ResultMap
包含了Discriminator
,Discriminator
包含了嵌套結果映射,所以ResultMap
包含嵌套結果映射,這個邏輯沒什么問題.
但是,有一點請注意,在判斷Discriminator
中是否包含嵌套結果映射時,有一個前置條件是被引用的嵌套結果映射必須已經存在于當前Configuration
對象中:
// ... 省略 ...
if (hasResultMap(discriminatedResultMapName)) {
// ... 處理嵌套結果映射 ...
}
// ... 省略 ...
這是因為,前面我們構建Discriminator
對象時,在解析case
元素的resultMap
屬性后,沒有進行任何校驗:
// 解析case代碼塊的ResultMap標記
String resultMap = caseChild.getStringAttribute("resultMap" /*使用指定的resultMap*/
, processNestedResultMappings(caseChild, resultMappings, resultType/*如果沒有指定resultMap,則動態生成ResultMap實例*/
)
);
因此,通過case
元素的resultMap
屬性引用的ResultMap
對象此時可能還沒有初始化.
如果被引用的ResultMap
對象還沒有初始化,checkLocallyForDiscriminatedNestedResultMaps()
方法就無法得知被引用的ResultMap
對象中是否包含嵌套結果映射,因此也就沒有辦法更新當前ResultMap
對象中的hasNestedResultMaps
標記.
checkGloballyForDiscriminatedNestedResultMaps()
方法是對checkLocallyForDiscriminatedNestedResultMaps()
方法在這種特殊場景下的一個補充.
protected void checkGloballyForDiscriminatedNestedResultMaps(ResultMap rm) {
if (rm.hasNestedResultMaps()) {
// 當前的ResultMap有嵌套ResultMap
for (Map.Entry<String, ResultMap> entry : resultMaps.entrySet()) {
// 遍歷處理全局已加載的resultMap
Object value = entry.getValue();
if (value instanceof ResultMap) {
ResultMap entryResultMap = (ResultMap) value;
if (!entryResultMap.hasNestedResultMaps() && entryResultMap.getDiscriminator() != null) {
// 已經加載了鑒別器對應的ResultMap
Collection<String> discriminatedResultMapNames = entryResultMap.getDiscriminator().getDiscriminatorMap().values();
// 獲取鑒別器引用了當前的resultMap
if (discriminatedResultMapNames.contains(rm.getId())) {
// 更新entryResultMap的hasNestedResultMaps字段為true.
entryResultMap.forceNestedResultMaps();
}
}
}
}
}
}
當注冊了一個包含嵌套結果映射的ResultMap
對象時,checkGloballyForDiscriminatedNestedResultMaps()
方法就會獲取所有通過鑒別器引用了當前ResultMap
對象的ResultMap
對象,并更新其hasNestedResultMaps
標記.
到這里,我們就完整的了解了創建和注冊ResultMap
對象的流程.
寫在最后
這篇文章相對比較復雜,涉及到的內容也比較多,因為邏輯比較復雜,涉及到的代碼層級也比較深,因此建議配合著源碼多看幾遍.
加油!
就醬,告辭!