目錄
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
的繼承體系很相似呢?最頂級介面是四大元件物件,分別有兩個實現類 BaseStatementHandler
和 RoutingStatementHandler
,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 方法的執行過程:
- MyBatis 接收到 update 請求後會先找到 CachingExecutor 快取執行器查詢是否需要重新整理快取,然後找到BaseExecutor 執行 update 方法;
- BaseExecutor 基礎執行器會清空一級快取,然後交給再根據執行器的型別找到對應的執行器,繼續執行 update 方法;
- 具體的執行器會先建立 Configuration 物件,根據 Configuration 物件呼叫 newStatementHandler 方法,返回 statementHandler 的控制程式碼;
- 具體的執行器會呼叫 prepareStatement 方法,找到本類的 prepareStatement 方法後,再有prepareStatement 方法呼叫 StatementHandler 的子類 BaseStatementHandler 中的 prepare 方法
- BaseStatementHandler 中的 prepare 方法會呼叫 instantiateStatement 例項化具體的 Statement 物件並返回給具體的執行器物件
- 由具體的執行器物件呼叫 parameterize 方法給引數進行賦值。
續上上面的 parameter
方法,具體交給 ParameterHandler
進行進一步的賦值處理
Query 查詢方法幾乎和 update 方法相同,這裡就不再詳細的舉例說明了