精盡MyBatis原始碼分析 - SQL執行過程(二)之 StatementHandler
阿新 • • 發佈:2020-11-25
> 該系列文件是本人在學習 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`會在後續講到