MyBatis 核心配置綜述之StatementHandler


MyBatis 核心配置綜述之StatementHandler

MyBatis 四大組件之StatementHandler

StatementHandler 是四大組件中最重要的一個對象,負責操作 Statement 對象與數據庫進行交流,在工作時還會使用 ParameterHandler 和 ResultSetHandler 對參數進行映射,對結果進行實體類的綁定

我們在搭建原生JDBC的時候,會有這樣一行代碼

Statement stmt = conn.createStatement(); //也可以使用PreparedStatement來做

這行代碼創建的 Statement 對象或者是 PreparedStatement 對象就是由StatementHandler進行管理的。

StatementHandler 的基本構成

來看一下StatementHandler中的主要方法:

  • prepare: 用於創建一個具體的 Statement 對象的實現類或者是 Statement 對象
  • parametersize: 用於初始化 Statement 對象以及對sql的占位符進行賦值
  • update: 用於通知 Statement 對象將 insert、update、delete 操作推送到數據庫
  • query: 用於通知 Statement 對象將 select 操作推送數據庫並返回對應的查詢結果

StatementHandler的繼承結構

有沒有感覺和 Executor 的繼承體系很相似呢?最頂級接口是四大組件對象,分別有兩個實現類 BaseStatementHandlerRoutingStatementHandler ,BaseStatementHandler 有三個實現類, 他們分別是 SimpleStatementHandler、PreparedStatementHandler 和 CallableStatementHandler。

RoutingStatementHandler: RoutingStatementHandler 並沒有對 Statement 對象進行使用,只是根據StatementType 來創建一個代理,代理的就是對應Handler的三種實現類。在MyBatis工作時,使用的StatementHandler 接口對象實際上就是 RoutingStatementHandler 對象.我們可以理解為

StatementHandler statmentHandler = new RountingStatementHandler();
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {

  // 根據 statementType 創建對應的 Statement 對象
  switch (ms.getStatementType()) {
    case STATEMENT:
      delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
      break;
    case PREPARED:
      delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
      break;
    case CALLABLE:
      delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
      break;
    default:
      throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
  }

}

BaseStatementHandler: 是 StatementHandler 接口的另一個實現類.本身是一個抽象類.用於簡化StatementHandler 接口實現的難度,屬於適配器設計模式體現,它主要有三個實現類

  • SimpleStatementHandler: 管理 Statement 對象並向數據庫中推送不需要預編譯的SQL語句
  • PreparedStatementHandler: 管理 Statement 對象並向數據中推送需要預編譯的SQL語句,
  • CallableStatementHandler:管理 Statement 對象並調用數據庫中的存儲過程

StatementHandler 對象創建以及源碼分析

StatementHandler 對象是在 SqlSession 對象接收到命令操作時,由 Configuration 對象中的newStatementHandler 負責調用的,也就是說 Configuration 中的 newStatementHandler 是由執行器中的查詢、更新(插入、更新、刪除)方法來提供的,StatementHandler 其實就是由 Executor 負責管理和創建的。

SimpleExecutor.java

public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      // 獲取環境配置
      Configuration configuration = ms.getConfiguration();
      // 創建StatementHandler,解析SQL語句
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      // 由handler來對SQL語句執行解析工作
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

由圖中可以看出,StatementHandler 默認創建一個 RoutingStatementHandler ,這也就是 StatementHandler 的默認實現,由 RoutingStatementHandler 負責根據 StatementType 創建對應的StatementHandler 來處理調用。

prepare方法調用流程分析

prepare 方法的調用過程是這樣的,在上面的源碼分析過程中,我們分析到了執行器 Executor 在執行SQL語句的時候會創建 StatementHandler 對象,進而經過一系列的 StatementHandler 類型的判斷並初始化。再拿到StatementHandler 返回的 statementhandler 對象的時候,會調用其prepareStatement()方法,下面就來一起看一下 preparedStatement() 方法(我們以簡單執行器為例,因為創建其 StatementHandler 對象的流程和執行 preparedStatement() 方法的流程是差不多的):

SimpleExecutor.java

public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
  Statement stmt = null;
  try {
    // 獲取環境配置
    Configuration configuration = ms.getConfiguration();
    // 創建StatementHandler,解析SQL語句
    StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
    
    stmt = prepareStatement(handler, ms.getStatementLog());
    
    // 由handler來對SQL語句執行解析工作
    return handler.<E>query(stmt, resultHandler);
  } finally {
    closeStatement(stmt);
  }
}


private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
  Statement stmt;
  Connection connection = getConnection(statementLog);
  
  stmt = handler.prepare(connection, transaction.getTimeout());
  
  handler.parameterize(stmt);
  return stmt;
}

// prepare方法調用到 StatementHandler 的實現類RoutingStatementHandler,再由RoutingStatementHandler調用BaseStatementHandler中的prepare 方法

// RoutingStatementHandler.java
@Override
  public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
    return delegate.prepare(connection, transactionTimeout);
  }

//  BaseStatementHandler.java
 @Override
  public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
    ErrorContext.instance().sql(boundSql.getSql());
    Statement statement = null;
    try {
      statement = instantiateStatement(connection);
      setStatementTimeout(statement, transactionTimeout);
      setFetchSize(statement);
      return statement;
    } ...

其中最重要的方法就是 instantiateStatement() 方法了,在得到數據庫連接 connection 的對象的時候,會去調用 instantiateStatement() 方法,instantiateStatement 方法位於 StatementHandler 中,是一個抽象方法由子類去實現,實際執行的是三種 StatementHandler 中的一種,我們還以 SimpleStatementHandler 為例

protected Statement instantiateStatement(Connection connection) throws SQLException {
    if (mappedStatement.getResultSetType() != null) {
      return connection.createStatement(mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
    } else {
      return connection.createStatement();
    }
  }

從上面代碼我們可以看到,instantiateStatement() 最終返回的也是Statement對象,經過一系列的調用會把statement 對象返回到 SimpleExecutor 簡單執行器中,為 parametersize 方法所用。也就是說,prepare 方法負責生成 Statement 實例對象,而 parameterize 方法用於處理 Statement 實例多對應的參數。

parametersize 方法調用流程分析

parametersize 方法看的就比較暢快了,也是經由執行器來管理 parametersize 的方法調用,這次我們還想以SimpleStatementHandler 為例但是卻不行了?為什么呢?因為 SimpleStatementHandler 是個空實現了,為什么是null呢?因為 SimpleStatementHandler 只負責處理簡單SQL,能夠直接查詢得到結果的SQL,例如:

select studenname from Student

而 SimpleStatementHandler 又不涉及到參數的賦值問題,那么參數賦值該在哪里進行呢?實際上為參數賦值這步操作是在 PreparedStatementHandler 中進行的,因此我們的主要關注點在 PreparedStatementHandler 中的parameterize 方法

public void parameterize(Statement statement) throws SQLException {
  parameterHandler.setParameters((PreparedStatement) statement);
}

我們可以看到,為參數賦值的工作是由一個叫做 parameterHandler 對象完成的,都是這樣的嗎?來看一下CallableStatementHandler

public void parameterize(Statement statement) throws SQLException {
  registerOutputParameters((CallableStatement) statement);
  parameterHandler.setParameters((CallableStatement) statement);
}

上面代碼可以看到,CallableStatementHandler 也是由 parameterHandler 進行參數賦值的。

那么這個 parameterHandler 到底是什么呢?這個問題能想到說明老兄你已經上道了,這也就是我們執行器的第三個組件。這個組件我們在下一節進行分析

update 方法調用流程分析

用一幅流程圖來表示一下這個調用過程:

簡單描述一下update 方法的執行過程:

  1. MyBatis 接收到 update 請求后會先找到 CachingExecutor 緩存執行器查詢是否需要刷新緩存,然后找到BaseExecutor 執行 update 方法;
  2. BaseExecutor 基礎執行器會清空一級緩存,然后交給再根據執行器的類型找到對應的執行器,繼續執行 update 方法;
  3. 具體的執行器會先創建 Configuration 對象,根據 Configuration 對象調用 newStatementHandler 方法,返回 statementHandler 的句柄;
  4. 具體的執行器會調用 prepareStatement 方法,找到本類的 prepareStatement 方法后,再有prepareStatement 方法調用 StatementHandler 的子類 BaseStatementHandler 中的 prepare 方法
  5. BaseStatementHandler 中的 prepare 方法會調用 instantiateStatement 實例化具體的 Statement 對象並返回給具體的執行器對象
  6. 由具體的執行器對象調用 parameterize 方法給參數進行賦值。

續上上面的 parameter方法,具體交給 ParameterHandler 進行進一步的賦值處理

Query 查詢方法幾乎和 update 方法相同,這里就不再詳細的舉例說明了


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM