MyBatis 核心配置綜述之StatementHandler

c旋兒發表於2019-08-03

MyBatis 核心配置綜述之StatementHandler

MyBatis 四大元件之StatementHandler

StatementHandler 是四大元件中最重要的一個物件,負責操作 Statement 物件與資料庫進行交流,在工作時還會使用 ParameterHandler 和 ResultSetHandler 對引數進行對映,對結果進行實體類的繫結

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

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

這行程式碼建立的 Statement 物件或者是 PreparedStatement 物件就是由StatementHandler進行管理的。

StatementHandler 的基本構成

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

MyBatis 核心配置綜述之StatementHandler

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

StatementHandler的繼承結構

MyBatis 核心配置綜述之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);
    }
  }

MyBatis 核心配置綜述之StatementHandler

由圖中可以看出,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 方法呼叫流程分析

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

MyBatis 核心配置綜述之StatementHandler

簡單描述一下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 方法相同,這裡就不再詳細的舉例說明了

相關文章