MyBatis 四大核心組件我們已經了解到了兩種,一個是 Executor ,它是MyBatis 解析SQL請求首先會經過的第一道關卡,它的主要作用在於創建緩存,管理 StatementHandler 的調用,為 StatementHandler 提供 Configuration 環境等。StatementHandler 組件最主要的作用在於創建 Statement 對象與數據庫進行交流,還會使用 ParameterHandler 進行參數配置,使用 ResultSetHandler 把查詢結果與實體類進行綁定。那么本篇就來了解一下第三個組件 ParameterHandler。
ParameterHandler 簡介
ParameterHandler
相比於其他的組件就簡單很多了,ParameterHandler 譯為參數處理器,負責為 PreparedStatement 的 sql 語句參數動態賦值,這個接口很簡單只有兩個方法
/**
* A parameter handler sets the parameters of the {@code PreparedStatement}
* 參數處理器為 PreparedStatement 設置參數
*/
public interface ParameterHandler {
Object getParameterObject();
void setParameters(PreparedStatement ps)
throws SQLException;
}
ParameterHandler 只有一個實現類 DefaultParameterHandler
, 它實現了這兩個方法。
- getParameterObject: 用於讀取參數
- setParameters: 用於對 PreparedStatement 的參數賦值
ParameterHandler 創建
參數處理器對象是在創建 StatementHandler 對象的同時被創建的,由 Configuration 對象負責創建
BaseStatementHandler.java
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();
if (boundSql == null) { // issue #435, get the key before calculating the statement
generateKeys(parameterObject);
boundSql = mappedStatement.getBoundSql(parameterObject);
}
this.boundSql = boundSql;
// 創建參數處理器
this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
// 創建結果映射器
this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
}
在創建 ParameterHandler 時,需要傳入SQL的mappedStatement 對象,讀取的參數和SQL語句
注意:一個 BoundSql 對象,就代表了一次sql語句的實際執行,而 SqlSource 對象的責任,就是根據傳入的參數對象,動態計算這個 BoundSql, 也就是 Mapper 文件中節點的計算,是由 SqlSource 完成的,SqlSource 最常用的實現類是 DynamicSqlSource
Configuration.java
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
// 創建ParameterHandler
ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
return parameterHandler;
}
上面是 Configuration 創建 ParameterHandler 的過程,它實際上是交由 LanguageDriver
來創建具體的參數處理器,LanguageDriver 默認的實現類是 XMLLanguageDriver
,由它調用 DefaultParameterHandler
中的構造方法完成 ParameterHandler 的創建工作
public ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
return new DefaultParameterHandler(mappedStatement, parameterObject, boundSql);
}
public DefaultParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
this.mappedStatement = mappedStatement;
this.configuration = mappedStatement.getConfiguration();
// 獲取 TypeHandlerRegistry 注冊
this.typeHandlerRegistry = mappedStatement.getConfiguration().getTypeHandlerRegistry();
this.parameterObject = parameterObject;
this.boundSql = boundSql;
}
上面的流程是創建 ParameterHandler 的過程,創建完成之后,該進行具體的解析工作,那么 ParameterHandler 如何解析SQL中的參數呢?SQL中的參數從哪里來的?
ParameterHandler 中的參數從何而來
你可能知道 Parameter 中的參數是怎么來的,無非就是從 Mapper 配置文件中映射過去的啊,就比如如下例子
參數肯定就是圖中標紅的 1 ,然后再傳到XML對應的 SQL 語句中,用 #{}
或者 ${}
來進行賦值啊,
嗯,你講的沒錯,可是你知道這個參數是如何映射過來的嗎?或者說你知道 Parameter 的解析過程嗎?或許你不是很清晰了,我們下面就來探討一下 ParameterHandler 對參數的解析,這其中涉及到 MyBatis 中的動態代理模式
在MyBatis 中,當 deptDao.findByDeptNo(1) 將要執行的時候,會被 JVM 進行攔截,交給 MyBatis 中的代理實現類 MapperProxy 的 invoke 方法中,這也是執行 SQL 語句的主流程。
然后交給 Executor 、StatementHandler進行對應的參數解析和執行,因為是帶參數的 SQL 語句,最終會創建 PreparedStatement 對象並創建參數解析器進行參數解析
SimpleExecutor.java
handler.parameterize(stmt) 最終會調用到 DefaultParameterHandler
中的 setParameters
方法,我在源碼上做了注釋,為了方便拷貝,我沒有采用截圖的形式
public void setParameters(PreparedStatement ps) {
ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
// parameterMappings 就是對 #{} 或者 ${} 里面參數的封裝
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings != null) {
// 如果是參數化的SQL,便需要循環取出並設置參數的值
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);
// 如果參數類型不是 OUT ,這個類型與 CallableStatementHandler 有關
// 因為存儲過程不存在輸出參數,所以參數不是輸出參數的時候,就需要設置。
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
// 得到#{} 中的屬性名
String propertyName = parameterMapping.getProperty();
// 如果 propertyName 是 Map 中的key
if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
// 通過key 來得到 additionalParameter 中的value值
value = boundSql.getAdditionalParameter(propertyName);
}
// 如果不是 additionalParameters 中的key,而且傳入參數是 null, 則value 就是null
else if (parameterObject == null) {
value = null;
}
// 如果 typeHandlerRegistry 中已經注冊了這個參數的 Class對象,即它是Primitive 或者是String 的話
else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
} else {
// 否則就是 Map
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
// 在通過SqlSource 的parse 方法得到parameterMappings 的具體實現中,我們會得到parameterMappings的typeHandler
TypeHandler typeHandler = parameterMapping.getTypeHandler();
// 獲取typeHandler 的jdbc type
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) {
jdbcType = configuration.getJdbcTypeForNull();
}
try {
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
} catch (SQLException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
}
}
}
}
}
ParameterHandler 解析
我們在 MyBatis 核心配置綜述之 StatementHandler
一文中了解到 Executor 管理的是 StatementHandler 對象的創建以及參數賦值,那么我們的主要入口還是 Executor 執行器
下面用一個流程圖表示一下 ParameterHandler 的解析過程,以簡單執行器為例
像是 doQuery
,doUpdate
,doQueryCursor
等方法都會先調用到
// 生成 preparedStatement 並調用 prepare 方法,並為參數賦值
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;
}
然后在生成 preparedStatement
調用DefaultParameterHandler
進行參數賦值。
公眾號提供 優質Java資料 以及CSDN免費下載 權限,歡迎你關注我