MyBatis原理第四篇——statementHandler對象(sqlSession內部核心實現,插件的基礎)


首先約定文中將的四大對象是指:executor, statementHandler,parameterHandler,resultHandler對象。(為了方便下面的文章說道四大對象就專指它們)

講到statementHandler,毫無疑問它是我們四大對象最重要的一個,它的任務就是和數據庫對話。在它這里會使用parameterHandler和ResultHandler對象為我們綁定SQL參數和組裝最后的結果返回。

一、statementHandler對象的定義:

首先我們先來看看statementHandler接口的定義:

 

[java]  view plain  copy
 
  1. public interface StatementHandler {  
  2.   
  3.   Statement prepare(Connection connection)  
  4.       throws SQLException;  
  5.   
  6.   void parameterize(Statement statement)  
  7.       throws SQLException;  
  8.   
  9.   void batch(Statement statement)  
  10.       throws SQLException;  
  11.   
  12.   int update(Statement statement)  
  13.       throws SQLException;  
  14.   
  15.   <E> List<E> query(Statement statement, ResultHandler resultHandler)  
  16.       throws SQLException;  
  17.   
  18.   BoundSql getBoundSql();  
  19.   
  20.   ParameterHandler getParameterHandler();  
  21.   
  22. }  

 

這里有幾個重要的方法,prepare,parameterize和query,update,他們的作用是不一樣的。

在MyBatis實現了statementHandler的有四個類:

RoutingStatementHandler,這是一個封裝類,它不提供具體的實現,只是根據Executor的類型,創建不同的類型StatementHandler。

SimpleStatementHandler,這個類對應於JDBC的Statement對象,用於沒有預編譯參數的SQL的運行。

PreparedStatementHandler 這個用於預編譯參數SQL的運行。

CallableStatementHandler 它將實存儲過程的調度。

 

在MyBatis中,Configuration對象會采用new RoutingStatementHandler()來生成StatementHandler對象,換句話說我們真正使用的是RoutingStatementHandler對象,然后它會根據Executor的類型去創建對應具體的statementHandler對象(SimpleStatementHandler,PreparedStatementHandler和CallableStatementHandler)。

然后利用具體statementHandler的方法完成所需要的功能。那么這個具體的statementHandler是保存在RoutingStatementHandler對象的delegate屬性的,所以當我們攔截statementHandler的時候就要常常訪問它了。它們的關系如下圖所示。

 

 

二、prepare方法

首先prepare方法是用來編譯SQL的,讓我們看看它的源碼實現。這里我們看到了BaseStatementHandler對prepare方法的實現,

 

[java]  view plain  copy
 
  1. @Override  
  2.  public Statement prepare(Connection connection) throws SQLException {  
  3.    ErrorContext.instance().sql(boundSql.getSql());  
  4.    Statement statement = null;  
  5.    try {  
  6.      statement = instantiateStatement(connection);  
  7.      setStatementTimeout(statement);  
  8.      setFetchSize(statement);  
  9.      return statement;  
  10.    } catch (SQLException e) {  
  11.      closeStatement(statement);  
  12.      throw e;  
  13.    } catch (Exception e) {  
  14.      closeStatement(statement);  
  15.      throw new ExecutorException("Error preparing statement.  Cause: " + e, e);  
  16.    }  
  17.  }  
  18.   
  19.  protected abstract Statement instantiateStatement(Connection connection) throws SQLException;  


顯然我們通過源碼更加關注抽象方法instantiateStatement是做了什么事情。它依舊是一個抽象方法,那么它就有其實現類。那就是之前說的那幾個具體的StatementHandler對象,讓我們看看PreparedStatementHandler:

 

 

[java]  view plain  copy
 
  1. @Override  
  2.   protected Statement instantiateStatement(Connection connection) throws SQLException {  
  3.     String sql = boundSql.getSql();  
  4.     if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {  
  5.       String[] keyColumnNames = mappedStatement.getKeyColumns();  
  6.       if (keyColumnNames == null) {  
  7.         return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);  
  8.       } else {  
  9.         return connection.prepareStatement(sql, keyColumnNames);  
  10.       }  
  11.     } else if (mappedStatement.getResultSetType() != null) {  
  12.       return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);  
  13.     } else {  
  14.       return connection.prepareStatement(sql);  
  15.     }  
  16.   }  

好這個方法非常簡單,我們可以看到它主要是根據上下文來預編譯SQL,這是我們還沒有設置參數。設置參數的任務是交由,statement接口的parameterize方法來實現的。

 

 

 

3、parameterize方法:

上面我們在prepare方法里面預編譯了SQL。那么我們這個時候希望設置參數。在Statement中我們是使用parameterize方法進行設置參數的。

讓我們看看PreparedStatementHandler中的parameterize方法:

 

[java]  view plain  copy
 
  1. @Override  
  2.   public void parameterize(Statement statement) throws SQLException {  
  3.     parameterHandler.setParameters((PreparedStatement) statement);  
  4.   }  

很顯然這里很簡單是通過parameterHandler來實現的,我們這篇文章只是停留在statementhandler的程度,等我們講解parameterHandler的時候再來看它如何實現吧,期待一下吧。

 

 

4、query/update方法

我們用了prepare方法預編譯了SQL,用了parameterize方法設置參數,那么我們接下來肯定是想執行SQL,而SQL無非是兩種:

一種是進行查詢——query,另外就是更新——update。

這些方法都很簡單,讓我們看看PreparedStatementHandler的實現:

 

[java]  view plain  copy
 
  1. @Override  
  2.   public int update(Statement statement) throws SQLException {  
  3.     PreparedStatement ps = (PreparedStatement) statement;  
  4.     ps.execute();  
  5.     int rows = ps.getUpdateCount();  
  6.     Object parameterObject = boundSql.getParameterObject();  
  7.     KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();  
  8.     keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);  
  9.     return rows;  
  10.   }  
  11.   
  12. ......  
  13. @Override  
  14.   public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {  
  15.     PreparedStatement ps = (PreparedStatement) statement;  
  16.     ps.execute();  
  17.     return resultSetHandler.<E> handleResultSets(ps);  
  18.   }  


我們可以看到如果是進行update的,它將會執行生成主鍵的操作(插入數據要自動生成主鍵的時候),然后就返回影響行數。

 

如果是進行query的就更加簡單了,它就是執行SQL語句,然后講結果使用resultHandler的handleResultSets去完成我們的結果組裝。至於resultHandler的內部實現還是很復雜的,值得期待哦。這里我們暫且不講等待下一章吧。

 

5、總結

 

StatementHandler是MyBatis四大對象里面最重要的對象,它的方法是十分重要的,也是我們插件的基礎。

 

當我們需要改變sql的時候,顯然我們要在預編譯SQL(prepare方法前加入修改的邏輯)。

當我們需要修改參數的時候我們可以在調用parameterize方法前修改邏輯。或者使用ParameterHandler來改造設置參數。

我們需要控制組裝結果集的時候,也可以在query方法前后加入邏輯,或者使用ResultHandler來改造組裝結果。

懂的這些方法,才能理解我需要攔截什么對象,如何處理插件,這是MyBatis的核心內容。

 


免責聲明!

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



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