我們之前介紹過了MyBatis 四大核心配置之 Executor、StatementHandler、 ParameterHandler,今天本文的主題是介紹一下 MyBatis 最后一個神器也就是 ResultSetHandler。那么開始我們的討論
ResultSetHandler 簡介
回想一下,一條 SQL 的請求過程會經過哪幾個步驟? 首先會經過 Executor 執行器,它主要負責管理創建 StatementHandler 對象,然后由 StatementHandler 對象做數據庫的連接以及生成 Statement 對象,並解析 SQL 參數,由 ParameterHandler 對象負責把 Mapper 方法中的參數映射到 XML 中的 SQL 語句中,那么是不是還少了一個步驟,就能完成一個完整的 SQL 請求了?沒錯,這最后一步就是 SQL 結果集的處理工作,也就是 ResultSetHandler
的主要工作
要了解 ResultSetHandler 之前,首先需要了解 ResultSetHandler的繼承關系以及基本方法
public interface ResultSetHandler {
// 處理結果集
<E> List<E> handleResultSets(Statement stmt) throws SQLException;
// 批量處理結果集
<E> Cursor<E> handleCursorResultSets(Statement stmt) throws SQLException;
// 處理存儲過程的結果集
void handleOutputParameters(CallableStatement cs) throws SQLException;
}
ResultSetHandler是一個接口,它只有一個默認的實現類,像是 ParameterHandler 一樣,它的默認實現類是DefaultResultSetHandler
ResultSetHandler 創建
ResultSetHandler 是在處理查詢請求的時候由 Configuration 對象負責創建,示例如下
protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
this.configuration = mappedStatement.getConfiguration();
this.executor = executor;
this.mappedStatement = mappedStatement;
this.rowBounds = rowBounds;
this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
this.objectFactory = configuration.getObjectFactory();
if (boundSql == null) { // issue #435, get the key before calculating the statement
generateKeys(parameterObject);
boundSql = mappedStatement.getBoundSql(parameterObject);
}
this.boundSql = boundSql;
// 創建參數處理器
this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
// 創建結果映射器
this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
}
public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
ResultHandler resultHandler, BoundSql boundSql) {
// 由 DefaultResultSetHandler 進行初始化
ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
return resultSetHandler;
}
上述的創建過程是對 ResultSetHandler 創建過程以及初始化的簡單解釋,下面是對具體的查詢請求進行分析
ResultSetHandler 處理結果映射
回想一下,我們在進行傳統crud操作的時候,哪些方法是需要返回值的?當然我們說的返回值指的是從數據庫中查詢出來的值,而不是標識符,應該只有查詢方法吧?所以 MyBatis 只針對 query 方法做了返回值的映射,代碼如下:
PreparedStatementHandler.java
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
// 處理結果集
return resultSetHandler.<E> handleResultSets(ps);
}
@Override
public <E> Cursor<E> queryCursor(Statement statement) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
// 批量處理結果集
return resultSetHandler.<E> handleCursorResultSets(ps);
}
CallableStatementHandler.java 處理存儲過程的SQL
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
CallableStatement cs = (CallableStatement) statement;
cs.execute();
List<E> resultList = resultSetHandler.<E>handleResultSets(cs);
resultSetHandler.handleOutputParameters(cs);
return resultList;
}
@Override
public <E> Cursor<E> queryCursor(Statement statement) throws SQLException {
CallableStatement cs = (CallableStatement) statement;
cs.execute();
Cursor<E> resultList = resultSetHandler.<E>handleCursorResultSets(cs);
resultSetHandler.handleOutputParameters(cs);
return resultList;
}
DefaultResultSetHandler 源碼解析
MyBatis 只有一個默認的實現類就是 DefaultResultSetHandler
,ResultSetHandler 主要負責處理兩件事
- 處理 Statement 執行后產生的結果集,生成結果列表
- 處理存儲過程執行后的輸出參數
按照 Mapper 文件中配置的 ResultType 或 ResultMap 來封裝成對應的對象,最后將封裝的對象返回即可。
來看一下主要的源碼:
@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);
// 如果ResultSet 包裝器不是null, 並且 resultmap 的數量 > resultSet 的數量的話
// 因為 resultSetCount 第一次肯定是0,所以直接判斷 ResultSetWrapper 是否為 0 即可
while (rsw != null && resultMapCount > resultSetCount) {
// 從 resultMap 中取出 resultSet 數量
ResultMap resultMap = resultMaps.get(resultSetCount);
// 處理結果集, 關閉結果集
handleResultSet(rsw, resultMap, multipleResults, null);
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
// 從 mappedStatement 取出結果集
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);
}
其中涉及的主要對象有:
ResultSetWrapper
: 結果集的包裝器,主要針對結果集進行的一層包裝,它的主要屬性有
- ResultSet : Java JDBC ResultSet接口表示數據庫查詢的結果。 有關查詢的文本顯示了如何將查詢結果作為java.sql.ResultSet返回。 然后迭代此ResultSet以檢查結果。
- TypeHandlerRegistry: 類型注冊器,TypeHandlerRegistry 在初始化的時候會把所有的 Java類型和類型轉換器進行注冊。
- ColumnNames: 字段的名稱,也就是查詢操作需要返回的字段名稱
- ClassNames: 字段的類型名稱,也就是 ColumnNames 每個字段名稱的類型
- JdbcTypes: JDBC 的類型,也就是java.sql.Types 類型
ResultMap
: 負責處理更復雜的映射關系
multipleResults
:
其中的主要方法是 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());
}
}
// 如果有嵌套的ResultMap 的話
// 確保沒有行綁定
// 檢查結果處理器
// 如果沒有的話,直接處理簡單的ResultMap
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);
}
}
handleResultSets 方法返回的是 collapseSingleResultList(multipleResults) ,它是什么呢?
private List<Object> collapseSingleResultList(List<Object> multipleResults) {
return multipleResults.size() == 1 ? (List<Object>) multipleResults.get(0) : multipleResults;
}
它是判斷的 multipleResults 的數量,如果數量是 1 ,就直接取位置為0的元素,如果不是1,那就返回 multipleResults 的真實數量
那么 multipleResults 的數量是哪來的呢?
它的值其實是處理結果集中傳遞進去的
handleResultSet(rsw, resultMap, multipleResults, null);
然后在處理結果集的方法中對 multipleResults 進行添加
multipleResults.add(defaultResultHandler.getResultList());
下面我們來看一下返回的真實實現類 DefaultResultSetHandler 中的結構組成
在 DefaultResultSetHandler 中處理完結果映射,並把上述結構返回給調用的客戶端,從而執行完成一條完整的SQL語句。
我的公眾號二維碼:歡迎關注
文章參考: