1. 程式人生 > >精盡MyBatis原始碼分析 - SQL執行過程(二)之 StatementHandler

精盡MyBatis原始碼分析 - SQL執行過程(二)之 StatementHandler

> 該系列文件是本人在學習 Mybatis 的原始碼過程中總結下來的,可能對讀者不太友好,請結合我的原始碼註釋([Mybatis原始碼分析 GitHub 地址](https://github.com/liu844869663/mybatis-3)、[Mybatis-Spring 原始碼分析 GitHub 地址](https://github.com/liu844869663/spring)、[Spring-Boot-Starter 原始碼分析 GitHub 地址](https://github.com/liu844869663/spring-boot-starter))進行閱讀 > > MyBatis 版本:3.5.2 > > MyBatis-Spring 版本:2.0.3 > > MyBatis-Spring-Boot-Starter 版本:2.1.4 ## MyBatis的SQL執行過程 在前面一系列的文件中,我已經分析了 MyBatis 的基礎支援層以及整個的初始化過程,此時 MyBatis 已經處於就緒狀態了,等待使用者發號施令了 那麼接下來我們來看看它執行SQL的整個過程,該過程比較複雜,涉及到二級快取,將返回結果轉換成 Java 物件以及延遲載入等等處理過程,這裡將一步一步地進行分析: - [**《SQL執行過程(一)之Executor》**](https://www.cnblogs.com/lifullmoon/p/14015111.html) - [**《SQL執行過程(二)之StatementHandler》**](https://www.cnblogs.com/lifullmoon/p/14015149.html) - **《SQL執行過程(三)之ResultSetHandler》** - **《SQL執行過程(四)之延遲載入》** MyBatis中SQL執行的整體過程如下圖所示:
在 SqlSession 中,會將執行 SQL 的過程交由`Executor`執行器去執行,過程大致如下: 1. 通過`DefaultSqlSessionFactory`建立與資料庫互動的 `SqlSession` “會話”,其內部會建立一個`Executor`執行器物件 2. 然後`Executor`執行器通過`StatementHandler`建立對應的`java.sql.Statement`物件,並通過`ParameterHandler`設定引數,然後執行資料庫相關操作 3. 如果是資料庫**更新**操作,則可能需要通過`KeyGenerator`先設定自增鍵,然後返回受影響的行數 4. 如果是資料庫**查詢**操作,則需要將資料庫返回的`ResultSet`結果集物件包裝成`ResultSetWrapper`,然後通過`DefaultResultSetHandler`對結果集進行對映,最後返回 Java 物件 上面還涉及到**一級快取**、**二級快取**和**延遲載入**等其他處理過程 ## SQL執行過程(二)之StatementHandler 在上一篇文件中,已經詳細地分析了在MyBatis的SQL執行過程中,SqlSession會話將資料庫操作交由Executor執行器去完成,實際上需要通過`StatementHandler`建立相應的`Statement`物件,並做一些準備工作,然後通過`Statement`執行資料庫操作,查詢結果則需要通過`ResultSetHandler`對結果集進行對映轉換成Java物件,那麼接下來我們先來看看`StatementHandler`到底做哪些操作 StatementHandler介面的實現類如下圖所示:
- `org.apache.ibatis.executor.statement.RoutingStatementHandler`:實現StatementHandler介面,裝飾器模式,根據Statement型別建立對應的StatementHandler物件,所有的方法執行交由該物件執行 - `org.apache.ibatis.executor.statement.BaseStatementHandler`:實現StatementHandler介面,提供骨架方法,指定的幾個抽象方法交由不同的子類去實現 - `org.apache.ibatis.executor.statement.SimpleStatementHandler`:繼承BaseStatementHandler抽象類,建立`java.sql.Statement`進行資料庫操作 - `org.apache.ibatis.executor.statement.PreparedStatementHandler`:繼承BaseStatementHandler抽象類,建立`java.sql.PreparedStatement`進行資料庫操作(預設) - `org.apache.ibatis.executor.statement.CallableStatementHandler`:繼承BaseStatementHandler抽象類,建立`java.sql.CallableStatement`進行資料庫操作,用於儲存過程 我們先回顧一下StatementHandler是在**哪裡被建立**的,可以在[**《SQL執行過程(一)之Executor》**](https://www.cnblogs.com/lifullmoon/p/14015111.html)的**SimpleExecutor**小節中有講到,建立的是`RoutingStatementHandler`物件 ### StatementHandler `org.apache.ibatis.executor.statement.StatementHandler`:Statement處理器介面,程式碼如下: ```java public interface StatementHandler { /** * 準備操作,可以理解成建立 Statement 物件 * * @param connection Connection 物件 * @param transactionTimeout 事務超時時間 * @return Statement 物件 */ Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException; /** * 設定 Statement 物件的引數 * * @param statement Statement 物件 */ void parameterize(Statement statement) throws SQLException; /** * 新增 Statement 物件的批量操作 * * @param statement Statement 物件 */ void batch(Statement statement) throws SQLException; /** * 執行寫操作 * * @param statement Statement 物件 * @return 影響的條數 */ int update(Statement statement) throws SQLException; /** * 執行讀操作 * * @param statement Statement 物件 * @param resultHandler ResultHandler 物件,處理結果 * @param 泛型 * @return 讀取的結果 */ List query(Statement statement, ResultHandler resultHandler) throws SQLException; /** * 執行讀操作,返回 Cursor 物件 * * @param statement Statement 物件 * @param 泛型 * @return Cursor 物件 */ Cursor queryCursor(Statement statement) throws SQLException; /** * @return BoundSql 物件 */ BoundSql getBoundSql(); /** * @return ParameterHandler 物件 */ ParameterHandler getParameterHandler(); } ``` 每個方法可以根據註釋先理解它的作用,在實現類中的會講到 ### RoutingStatementHandler `org.apache.ibatis.executor.statement.RoutingStatementHandler`:實現StatementHandler介面,採用裝飾器模式,在初始化的時候根據Statement型別,建立對應的StatementHandler物件,程式碼如下: ```java public class RoutingStatementHandler implements StatementHandler { private final StatementHandler delegate; public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { // 根據不同的型別,建立對應的 StatementHandler 實現類 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()); } } } ``` - 在建構函式中初始化`delegate`委託物件,根據`MappedStatement`(每個SQL對應的物件)的`statementType`型別,建立對應的StatementHandler實現類 - 其餘所有的方法都是直接交由`delegate`去執行的,這裡就不列出來了,就是實現StatementHandler介面的方法 回顧到[**《MyBatis初始化(二)之載入Mapper介面與XML對映檔案》**](https://www.cnblogs.com/lifullmoon/p/14015046.html)中的**XMLStatementBuilder**小節,在`parseStatementNode`方法中的第`10`步如下: ```java StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString())); ``` 所以說`statementType`的預設值為`PREPARED`,委託物件也就是`PreparedStatementHandler`型別 ### BaseStatementHandler `org.apache.ibatis.executor.statement.BaseStatementHandler`:實現StatementHandler介面,提供骨架方法,指定的幾個抽象方法交由不同的子類去實現 #### 構造方法 ```java public abstract class BaseStatementHandler implements StatementHandler { /** * 全域性配置 */ protected final Configuration configuration; /** * 例項工廠 */ protected final ObjectFactory objectFactory; /** * 型別處理器登錄檔 */ protected final TypeHandlerRegistry typeHandlerRegistry; /** * 執行結果處理器 */ protected final ResultSetHandler resultSetHandler; /** * 引數處理器,預設 DefaultParameterHandler */ protected final ParameterHandler parameterHandler; /** * 執行器 */ protected final Executor executor; /** * SQL 相關資訊 */ protected final MappedStatement mappedStatement; /** * 分頁條件 */ protected final RowBounds rowBounds; /** * SQL 語句 */ protected BoundSql boundSql; 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(); // <1> 如果 boundSql 為空,更新資料庫的操作這裡傳入的物件會為 null if (boundSql == null) { // issue #435, get the key before calculating the statement // <1.1> 生成 key,定義了
且配置了 order="BEFORE",則在 SQL 執行之前執行 generateKeys(parameterObject); // <1.2> 建立 BoundSql 物件 boundSql = mappedStatement.getBoundSql(parameterObject); } this.boundSql = boundSql; // <2> 建立 ParameterHandler 物件,預設為 DefaultParameterHandler // PreparedStatementHandler 實現的 parameterize 方法中需要對引數進行預處理,進行引數化時需要用到 this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql); // <3> 建立 DefaultResultSetHandler 物件 this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql); } } ``` 關於它的屬性可以根據註釋進行理解 1. 如果入參中的`boundSql`為`null`,則需要進行初始化,可以會看到`SimpleExecutor`中執行資料庫的更新操作時,傳入的`boundSql`為`null`,資料庫的查詢操作才會傳入該物件的值 1. 呼叫`generateKeys(Object parameter)`方法,根據配置的`KeyGenerator`物件,在SQL執行之前執行查詢操作獲取值,設定到入參物件對應屬性中,程式碼如下: ```java protected void generateKeys(Object parameter) { /* * 獲得 KeyGenerator 物件 * 1. 配置了 則會生成 SelectKeyGenerator 物件 * 2. 配置了 useGeneratedKeys="true" 則會生成 Jdbc3KeyGenerator 物件 * 否則為 NoKeyGenerator 物件 */ KeyGenerator keyGenerator = mappedStatement.getKeyGenerator(); ErrorContext.instance().store(); // 前置處理,建立自增編號到 parameter 中 keyGenerator.processBefore(executor, mappedStatement, null, parameter); ErrorContext.instance().recall(); } ``` 只有配置的``標籤**才有前置處理**,這就是為什麼資料庫的更新操作傳入的`boundSql`為`null`的原因,因為入參中有的屬性值可能需要提前生成一個值(執行配置的SQL語句),`KeyGenerator`會在後續講到