在在上一章內(nèi)容中我們還了關(guān)于KeyGenerator的技術(shù)債,下面還有這些技術(shù)債:
parameterHandler
resultSetHandler
今天我們就來償還關(guān)于resultSetHandler的內(nèi)容驶睦。
1. ResultSetHandler解析
首先這是一個(gè)接口顾瞪,我們先來看下這個(gè)源碼:
public interface ResultSetHandler {
/**
* 處理結(jié)果映射
*/
<E> List<E> handleResultSets(Statement stmt) throws SQLException;
/**
* 處理游標(biāo)結(jié)果映射节视,我不太常用,不做展開
*/
<E> Cursor<E> handleCursorResultSets(Statement stmt) throws SQLException;
/**
* 處理存儲過程結(jié)果映射退敦,我不太常用叽掘,不做展開
*/
void handleOutputParameters(CallableStatement cs) throws SQLException;
}
這里其實(shí)我們最常用的也就一個(gè)方法:handleResultSets。
我們再來看下它的映射關(guān)系別以為看到就一個(gè)繼承方法就可以松口氣道偷,打開一看這個(gè)類嚇?biāo)滥銅~~
不過還是要硬著頭皮去看缀旁,我們還是一步一步來,首先從它的構(gòu)造方法開始:
public DefaultResultSetHandler(Executor executor, MappedStatement mappedStatement, ParameterHandler parameterHandler, ResultHandler<?> resultHandler, BoundSql boundSql,
RowBounds rowBounds) {
this.executor = executor;
this.configuration = mappedStatement.getConfiguration();
this.mappedStatement = mappedStatement;
this.rowBounds = rowBounds;
this.parameterHandler = parameterHandler;
this.boundSql = boundSql;
this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
this.objectFactory = configuration.getObjectFactory();
this.reflectorFactory = configuration.getReflectorFactory();
this.resultHandler = resultHandler;
}
這里都還行勺鸦,我們再來看他的主要方法:handleResultSets
@Override
public List<Object> handleResultSets(Statement stmt) throws SQLException {
ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
final List<Object> multipleResults = new ArrayList<>();
int resultSetCount = 0;
//獲取數(shù)據(jù)庫結(jié)果并巍,并裝飾了下
ResultSetWrapper rsw = getFirstResultSet(stmt);
//獲取自己配置的ResultMap
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
//檢查rsw與resultMapCount是否合理
validateResultMapsCount(rsw, resultMapCount);
//這里邏輯是遍歷resultMaps或rsw,一般來說不是存儲過程我們的返回結(jié)果只有一個(gè)Object
//這里的理解是一個(gè)Object可能是List换途,包含多條記錄
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);
}
這個(gè)方法乍一看挺少懊渡,但是里面的調(diào)用比較深,所以看起來會很費(fèi)勁军拟,所以我們已理解整個(gè)思路為主剃执,而不過渡關(guān)注細(xì)節(jié)。
首先我們第一個(gè)方法: ResultSetWrapper rsw = getFirstResultSet(stmt);
這我們先不做過多深入懈息,記在技術(shù)債里肾档,只要知道這個(gè)是封裝數(shù)據(jù)庫結(jié)果的。
之后在最重要的就是handleResultSet方法辫继,我們來進(jìn)入:
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 {
//對應(yīng)resultMap進(jìn)入此處
if (resultHandler == null) {
DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
multipleResults.add(defaultResultHandler.getResultList());
} else {
//這邊個(gè)人感覺除了存儲過程是不會進(jìn)來的怒见,因?yàn)橹罢f了rsw一個(gè)只有一個(gè)值,而resultHandler大多數(shù)不指定姑宽,初始都會null
handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
}
}
} finally {
// issue #228 (close resultsets)
closeResultSet(rsw.getResultSet());
}
}
這里會跳到我們下一個(gè)關(guān)鍵方法handleRowValues來處理行數(shù)據(jù):
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<>();
ResultSet resultSet = rsw.getResultSet();
//根據(jù)rowBounds選定相應(yīng)的值遣耍,這里看出我們值分頁都是在應(yīng)用層而非數(shù)據(jù)庫層
skipRows(resultSet, rowBounds);
while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
// 根據(jù)該行記錄以及 ResultMap.discriminator ,決定映射使用的 ResultMap 對象
ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
// 根據(jù)最終確定的 ResultMap 對 ResultSet 中的該行記錄進(jìn)行映射炮车,得到映射后的結(jié)果對象
Object rowValue = getRowValue(rsw, discriminatedResultMap, null);
// 將映射創(chuàng)建的結(jié)果對象添加到 ResultHandler.resultList 中保存
storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
}
}
- resolveDiscriminatedResultMap方法我們在一般使用中不會進(jìn)入舵变,所以不進(jìn)行深入分析
- getRowValue 是實(shí)際映射行數(shù)據(jù)酣溃,所以我們進(jìn)行重點(diǎn)查看
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
final ResultLoaderMap lazyLoader = new ResultLoaderMap();
// 創(chuàng)建映射后的結(jié)果對象,一般是初始化棋傍,還沒賦值
Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
final MetaObject metaObject = configuration.newMetaObject(rowValue);
boolean foundValues = this.useConstructorMappings;
//判斷是否開啟自動映射功能救拉,默認(rèn)不開啟嵌套的自動映射
if (shouldApplyAutomaticMappings(resultMap, false)) {
//自動映射未明確的列
foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
}
//映射 ResultMap 中明確映射的列
foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
foundValues = lazyLoader.size() > 0 || foundValues;
rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
}
return rowValue;
}
這里的步驟就和我們自己創(chuàng)建一個(gè)結(jié)果映射對象的順序差不多了:
- 首先實(shí)例化一個(gè)結(jié)果類
- 開始根據(jù)結(jié)果插入進(jìn)行對應(yīng)的數(shù)據(jù)庫值
那么下面我們就來看他是如何實(shí)現(xiàn)這個(gè)的自動化的。
先來看下如果開啟了自動化映射功能之后applyAutomaticMappings的方法:
private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
// 獲得 UnMappedColumnAutoMapping 數(shù)組
List<UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);
boolean foundValues = false;
if (!autoMapping.isEmpty()) {
for (UnMappedColumnAutoMapping mapping : autoMapping) {
//從resultSet中獲取對應(yīng)column對應(yīng)的值
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')
//給對應(yīng)映射對象賦值
metaObject.setValue(mapping.property, value);
}
}
}
return foundValues;
}
這里關(guān)于createAutomaticMappings方法的邏輯我們也不深入了瘫拣,我們來看下UnMappedColumnAutoMapping理解他干了哪些就行:
private static class UnMappedColumnAutoMapping {
/**
* 字段名
*/
private final String column;
/**
* 屬性名
*/
private final String property;
/**
* TypeHandler 處理器
*/
private final TypeHandler<?> typeHandler;
/**
* 是否為基本屬性
*/
private final boolean primitive;
public UnMappedColumnAutoMapping(String column, String property, TypeHandler<?> typeHandler, boolean primitive) {
this.column = column;
this.property = property;
this.typeHandler = typeHandler;
this.primitive = primitive;
}
}
這里理解一下邏輯,遍歷未 mapped 的字段的名字的數(shù)組告喊,映射每一個(gè)字段在結(jié)果對象的相同名字的屬性麸拄,最終生成 UnMappedColumnAutoMapping 對象。
看到自動化映射之后黔姜,我們再找來看下默認(rèn)的映射方式applyPropertyMappings:
private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, ResultLoaderMap lazyLoader, String columnPrefix)
throws SQLException {
// 獲得 mapped 的字段的名字的數(shù)組
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) {
//獲取對應(yīng)的值
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 == DEFERRED) {
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;
}
這個(gè)方法的邏輯比較簡單拢切,通過我們設(shè)置的resultMap配置來獲取數(shù)據(jù)庫對應(yīng)的值并映射進(jìn)去。這里不管是什么方式秆吵,我們可以看到映射方法都是通過metaObject.setValue(property, value);
上面還有一個(gè)比較關(guān)鍵的方法getPropertyMappingValue:
private Object getPropertyMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix)
throws SQLException {
//嵌套查詢
if (propertyMapping.getNestedQueryId() != null) {
return getNestedQueryMappingValue(rs, metaResultObject, propertyMapping, lazyLoader, columnPrefix);
} else if (propertyMapping.getResultSet() != null) {
//存儲過程相關(guān)淮椰,忽略
addPendingChildRelation(rs, metaResultObject, propertyMapping); // TODO is that OK?
return DEFERRED;
} else {
//直接獲取
final TypeHandler<?> typeHandler = propertyMapping.getTypeHandler();
final String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
return typeHandler.getResult(rs, column);
}
}
2.今日總結(jié)
今天我們主要分析的是MyBatis是如何自動映射結(jié)果對象的,設(shè)計(jì)到的過程也相對比較負(fù)責(zé)纳寂,中心思想就是通過resultMap的配置主穗,去數(shù)據(jù)庫取,并通過MeteObject輔助類反射進(jìn)去值毙芜。
這里我們又欠下了關(guān)于ResultSetWrapper的技術(shù)債忽媒,我們現(xiàn)在來整理下:
parameterHandler
ResultSetWrapper