1. 程式人生 > >MyBatis 核心配置綜述之 ResultSetHandler

MyBatis 核心配置綜述之 ResultSetHandler

目錄

    • ResultSetHandler 簡介
    • ResultSetHandler 建立
    • ResultSetHandler 處理結果對映
    • DefaultResultSetHandler 原始碼解析

我們之前介紹過了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 主要負責處理兩件事

  1. 處理 Statement 執行後產生的結果集,生成結果列表
  2. 處理儲存過程執行後的輸出引數

按照 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語句。

我的公眾號二維碼:歡迎關注

文章參考:

https://blog.csdn.net/qq924862077/article/details/5270