MyBatis源碼分析之@ResultMap註解詳解
在前一篇文章講**@MapKey註解時,我原想將@ResultMap註解也一起拿出來說一下,但是發現@ResultMap解析加載源碼非常多,想想就不在一篇文章中講了,分開單獨來說,這一篇就來徹底探索一下@ResultMap**註解。
1. 加載過程
說到解析Mapper方法上的註解**@ResultMap**,這個就要回到解析configuration中的parseMapper位置了,在mapperRegistry加入當前解析出的mapper時我們知此處不僅做了加載mapper的事情,還進行了非xml方法配置時的加載。
public <T> void addMapper(Class<T> type) {
mapperRegistry.addMapper(type);
}
在此步addMapper之後,還進行了MapperAnnotationBuilder的解析。
knownMappers.put(type, new MapperProxyFactory<T>(type));
// It's important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the
// mapper parser. If the type is already known, it won't try.
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
@ResultMap的解析就在parse方法中,轉到parse方法。
public void parse() {
String resource = type.toString();
if (!configuration.isResourceLoaded(resource)) {
loadXmlResource();
configuration.addLoadedResource(resource);
assistant.setCurrentNamespace(type.getName());
parseCache();
parseCacheRef();
Method[] methods = type.getMethods();
for (Method method : methods) {
try {
// issue #237
if (!method.isBridge()) {
parseStatement(method);
}
} catch (IncompleteElementException e) {
configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}
}
進入parseStatement(method)方法中。
void parseStatement(Method method) {
.....
String resultMapId = null;
ResultMap resultMapAnnotation = method.getAnnotation(ResultMap.class);
if (resultMapAnnotation != null) {
String[] resultMaps = resultMapAnnotation.value();
StringBuilder sb = new StringBuilder();
for (String resultMap : resultMaps) {
if (sb.length() > 0) {
sb.append(",");
}
sb.append(resultMap);
}
resultMapId = sb.toString();
} else if (isSelect) {
resultMapId = parseResultMap(method);
}
assistant.addMappedStatement(
mappedStatementId,
resultMapId,
getReturnType(method),
resultSetType,
flushCache
);
}
上述方法中對@ResultMap註解進行了解析,並生成resultMapId,這的操作很有意思,如果ResultMap中需要多種返回的話,@ResultMap中的value是一個數組,可以傳多個值進去,然後生成resultMapId時拼接到一起。@ResultMap傳入value爲多個時寫法如下:
@MapKey("id")
@ResultMap({"BaseResultMap", "BaseResultMap2"})
@Select("select * from user where hotel_address = #{address};")
Map<Long, User> getUserByAddress(@Param("address") String address);
那麼生成的resultMapId爲"BaseResultMap,BaseResultMap2"。這倒是挺有意思,但是我一般也沒見過這樣寫的,找個時間我嘗試了,有什麼特別地方的話我就回來補充到這下面。
在生成resultMapId後將其他參數一起生成MappedStatement對象並保存進mappedStatements中。
public void addMappedStatement(MappedStatement ms) {
mappedStatements.put(ms.getId(), ms);
}
這裏使用的key也就是MappedStatement的id,也就是我們在之前文章中說到的id是用當前類名加方法名組裝而成的,具體過程在之前的parseStatement中。
void parseStatement(Method method) {
Options options = method.getAnnotation(Options.class);
final String mappedStatementId = type.getName() + "." + method.getName();
}
我原想分析到這就完了,但是這裏面僅僅只是得到了resultMapId字段,但是在後面使用的時候實際上是直接取出了整個ResultMap映射關係,所以還要繼續看上述parseStatement方法中的assistant.addMappedStatement方法。
public MappedStatement addMappedStatement(
String id,
SqlSource sqlSource,
StatementType statementType,
SqlCommandType sqlCommandType,
Integer fetchSize,
Integer timeout,
String parameterMap,
Class<?> parameterType,
String resultMap,
Class<?> resultType,
ResultSetType resultSetType,
boolean flushCache,
boolean useCache,
boolean resultOrdered,
KeyGenerator keyGenerator,
String keyProperty,
String keyColumn,
String databaseId,
LanguageDriver lang,
String resultSets) {
if (unresolvedCacheRef) {
throw new IncompleteElementException("Cache-ref not yet resolved");
}
id = applyCurrentNamespace(id, false);
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
.resource(resource)
.fetchSize(fetchSize)
.timeout(timeout)
.statementType(statementType)
.keyGenerator(keyGenerator)
.keyProperty(keyProperty)
.keyColumn(keyColumn)
.databaseId(databaseId)
.lang(lang)
.resultOrdered(resultOrdered)
.resultSets(resultSets)
.resultMaps(getStatementResultMaps(resultMap, resultType, id))
.resultSetType(resultSetType)
.flushCacheRequired(valueOrDefault(flushCache, !isSelect))
.useCache(valueOrDefault(useCache, isSelect))
.cache(currentCache);
ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
if (statementParameterMap != null) {
statementBuilder.parameterMap(statementParameterMap);
}
MappedStatement statement = statementBuilder.build();
configuration.addMappedStatement(statement);
return statement;
}
addMappedStatement還有一個操作getStatementResultMaps(resultMap, resultType, id),這一步用來獲取resultMaps集合。
private List<ResultMap> getStatementResultMaps(
String resultMap,
Class<?> resultType,
String statementId) {
resultMap = applyCurrentNamespace(resultMap, true);
List<ResultMap> resultMaps = new ArrayList<ResultMap>();
if (resultMap != null) {
String[] resultMapNames = resultMap.split(",");
for (String resultMapName : resultMapNames) {
try {
resultMaps.add(configuration.getResultMap(resultMapName.trim()));
} catch (IllegalArgumentException e) {
throw new IncompleteElementException("Could not find result map " + resultMapName, e);
}
}
} else if (resultType != null) {
ResultMap inlineResultMap = new ResultMap.Builder(
configuration,
statementId + "-Inline",
resultType,
new ArrayList<ResultMapping>(),
null).build();
resultMaps.add(inlineResultMap);
}
return resultMaps;
}
這一步中有兩個操作需要關注一下,一個是resultMap對象是從configuration.getResultMap(resultMapName.trim())中取出來的,而configuration中的resultMap是在解析xml時解析ResultMap節點從而初始化的。這一步完結,還有一步比較關鍵的是在構造ResultMap時,最後一個字段賦值爲null,而這個字段名爲autoMapping,這個比較重要,在後文映射字段值時需要用到,這個到時再說。
2. 字段映射
在解析完@ResultMap之後,現在就是在查詢完之後字段映射時候發揮作用了,在session查詢中找到selectList查詢方法,繼續追蹤到Executor的query方法,最終到SimpleStatementHandler中的query方法。
不過在直接看query方法之前還要再回頭來看下selectList方法。
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
這一步有個操作就是獲取到MappedStatement對象, 從許多前文中我們知MappedStatement對象中存放着Sql、ResultMap、timeout等等參數,而在後文中就需要從MappedStatement對象中取出ResultMap中,這個等會再說,先看query方法。
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
String sql = boundSql.getSql();
statement.execute(sql);
return resultSetHandler.<E>handleResultSets(statement);
}
在statement執行完query方法後,剩下的就是處理結果集以我們想要的形式返回,這一步的處理在handleResultSets中。
@Override
public List<Object> handleResultSets(Statement stmt) throws SQLException {
ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
final List<Object> multipleResults = new ArrayList<Object>();
int resultSetCount = 0;
ResultSetWrapper rsw = getFirstResultSet(stmt);
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
validateResultMapsCount(rsw, resultMapCount);
while (rsw != null && resultMapCount > resultSetCount) {
ResultMap resultMap = resultMaps.get(resultSetCount);
handleResultSet(rsw, resultMap, multipleResults, null);
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
String[] resultSets = mappedStatement.getResultSets();
if (resultSets != null) {
while (rsw != null && resultSetCount < resultSets.length) {
ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
if (parentMapping != null) {
String nestedResultMapId = parentMapping.getNestedResultMapId();
ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
handleResultSet(rsw, resultMap, null, parentMapping);
}
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
}
return collapseSingleResultList(multipleResults);
}
這裏就要說到前文中的MappedStatement對象了,這裏取出了ResultMap集合,然後在遍歷rsw中,對rsw記錄與resultMap中字段進行映射,進入到handleResultSet方法中。
private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
try {
if (parentMapping != null) {
handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
} else {
if (resultHandler == null) {
DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
multipleResults.add(defaultResultHandler.getResultList());
} else {
handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
}
}
} finally {
// issue #228 (close resultsets)
closeResultSet(rsw.getResultSet());
}
}
處理每一行的數據在handleRowValues方法中,進入handleRowValues方法中。
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);
}
}
private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
throws SQLException {
DefaultResultContext<Object> resultContext = new DefaultResultContext<Object>();
skipRows(rsw.getResultSet(), rowBounds);
while (shouldProcessMoreRows(resultContext, rowBounds) && rsw.getResultSet().next()) {
ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, null);
Object rowValue = getRowValue(rsw, discriminatedResultMap);
storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet());
}
}
最後的結果處理在getRowValue中,這裏返回的Object對象其實就是對應查詢出來的對象類了,在這就是user對象,getRowValue應該就是對具體的字段進行映射與賦值了,還是進去看一下吧。
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap) throws SQLException {
final ResultLoaderMap lazyLoader = new ResultLoaderMap();
Object rowValue = createResultObject(rsw, resultMap, lazyLoader, null);
if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
final MetaObject metaObject = configuration.newMetaObject(rowValue);
boolean foundValues = this.useConstructorMappings;
if (shouldApplyAutomaticMappings(resultMap, false)) {
foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, null) || foundValues;
}
foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, null) || foundValues;
foundValues = lazyLoader.size() > 0 || foundValues;
rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
}
return rowValue;
}
createResultObject這一步實質就是根據類型創建返回對象,如果可以自動映射關係的話,就在applyAutomaticMappings(rsw, resultMap, metaObject, null)這一步進行字段映射,可以進這個方法中看下是如何進行一步步映射字段value的。
private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
List<UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);
boolean foundValues = false;
if (!autoMapping.isEmpty()) {
for (UnMappedColumnAutoMapping mapping : autoMapping) {
final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);
if (value != null) {
foundValues = true;
}
if (value != null || (configuration.isCallSettersOnNulls() && !mapping.primitive)) {
// gcode issue #377, call setter on nulls (value is not 'found')
metaObject.setValue(mapping.property, value);
}
}
}
return foundValues;
}
createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);此處獲取到所有的映射關係。然後根據映射關係取出value然後set到metaObject中。
回到getRowValue方法中,在自動映射之後還有一步就是根據屬性映射,然後根據前文知此處我們應用的應該是屬性映射,就是說上一步自動映射其實進入不了if (shouldApplyAutomaticMappings(resultMap, false))方法中,所以此處還要繼續看applyPropertyMappings方法。
private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, ResultLoaderMap lazyLoader, String columnPrefix)
throws SQLException {
final List<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
boolean foundValues = false;
final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
for (ResultMapping propertyMapping : propertyMappings) {
String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
if (propertyMapping.getNestedResultMapId() != null) {
// the user added a column attribute to a nested result map, ignore it
column = null;
}
if (propertyMapping.isCompositeResult()
|| (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH)))
|| propertyMapping.getResultSet() != null) {
Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix);
// issue #541 make property optional
final String property = propertyMapping.getProperty();
if (property == null) {
continue;
} else if (value == DEFERED) {
foundValues = true;
continue;
}
if (value != null) {
foundValues = true;
}
if (value != null || (configuration.isCallSettersOnNulls() && !metaObject.getSetterType(property).isPrimitive())) {
// gcode issue #377, call setter on nulls (value is not 'found')
metaObject.setValue(property, value);
}
}
}
return foundValues;
}
這一步做的就是從resultMap中取出數據庫中表字段集合mappedColumnNames。然後對字段映射關係集合propertyMappings進行遍歷。用上述mappedColumnNames判斷column是否在mappedColumnNames中,我覺得這一步其實可以省了,column都是從resultMap中的映射關係中取出來的,而mappedColumnNames是從resultMap中取到的,這兩個基本是等同的,這裏有空以後再看下吧。
接下來其實就沒多少說的了,根據字段映射取到對應值,然後進行set操作,最後返回metaObject,組裝成list集合後返回給調用端。
上面文章中,其實要可以說的還挺多的,比如說多ResultMap返回,自動映射、註解@Results、@Result自定義映射關係等等,這個以後有空抽時間也來講一講吧。