概述
職責
首先了解一下statementHandler職責:主要負責處理MyBatis與JDBC之間Statement的交互,通俗而言就是負責操作Statement對象與數據庫之間的交互。其執行過程中主要依賴ParameterHandler和ResultSetHandler進行參數綁定和結果實體類綁定。
類圖
- BaseStatementHandler:StatementHandler接口的抽象實現類,主要用於簡化StatementHandler接口的實現難度,適配
器設計模式的體現,主要有三個實現類:
- SimpleStatementHandler:管理Statement對象並向數據庫推送不需要預編譯的SQL語句;
- PreparedStatementHandler:管理Statement對象並向數據庫推送需要預編譯的SQL語句;
- CallableStatementHandler:管理Statement對象並調用數據庫中的存儲過程;
- RoutingStatementHandler:StatementHandler接口的另一個實現類,並沒有對Statement對象其實際執行作用,只是根據StatementType來創建一個代理,代理的主要對象即對應BaseStatementHandler的三種實現類。
從源碼層面理解RoutingStatementHandler:
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
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());
}
}
源碼
下面主要以查詢爲例,debugger源碼層面分析。SQL執行開始皆從SqlSession開始。
- DefaultSqlSession
該類中針對增刪改查存在多個重載方法,以selectList爲例;
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
//調用Executor中的query
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
- BaseExecutor
/**
* 查詢方法,專門提供select執行的方法
*/
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
//獲取查詢SQL
BoundSql boundSql = ms.getBoundSql(parameter);
//創建緩存的key,即作爲HashMap中的key
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
//執行查詢
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
此段代碼塊中涉及到BoundSQL對象,此處簡單提一下相關概念,後期有時間專門總結一下BoundSQl.
BoundSQl對象主要是用於存儲SQL語句,以及對應的參數相關對象。
繼續調用BaseExcutor中的重載query方法:
/**
* 執行查詢邏輯,
* 首先從緩存中獲取數據,緩存中有數據則進行處理存儲過程;
* 如果緩存中沒有數據,則交互數據庫查詢數據,則將查詢結果添加到緩存中
*/
@SuppressWarnings("unchecked")
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
//如果不是嵌套查詢,且動態查詢語句中flushCache = true時即<select id= "xx" flushCache = true>纔會清空緩存
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
//嵌套查詢層數+1
queryStack++;
//首先從一級緩存中進行查詢:根據key獲取對象
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
//從緩存中獲取對象,則進行處理存儲過程
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
//否則即緩存中沒有對應的數據,則交互數據庫從數據庫中查詢數據
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
繼續跟進queryFromDatabase方法,該方法主要從數據庫中查詢數據
/**
* 交互數據庫從數據庫中查詢數據,再把查詢結果添加到緩存中
*/
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
//向緩存中添加佔位符,此時緩存中沒有真正所需要的查詢數據
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
//查詢數據庫,由其子類實現,獲取對應的查詢數據
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
//刪除佔位符
localCache.removeObject(key);
}
//再將從數據庫查詢的結果添加到一級緩存中
localCache.putObject(key, list);
//處理存儲過程
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
繼續跟進doQuery方法,發現BaseExecutor抽象類中該方法並沒有實體,僅提供一個鉤子方法,而是交給其子類實現,這裏體現了模板設計模式。
/**交互數據庫,查詢數據*/
protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
throws SQLException;
StatementHandler對象創建
- SimpleExcutor
從BaseExcutor類中跟進其子類SimpleExecutor看doQuery方法如何實現?
@Override
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();
//Configuration中獲取StatementHandler
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
到目前爲止,我們發現了StatementHandler對象的來源自Configuration中newStatementHandler方法創建;
驚喜若現,繼續跟進去;
- configuration
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
//創建StatementHandler
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
最終,我們可以明確地發現StatementHandler對象是由其子類RoutingStatementHandler創建的,那麼它創建的具體邏輯又是如何的呢?真相即將浮出水面,我們跟進RoutingStatementHandler的構造函數;
- RoutingStatementHandler
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
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());
}
}
看到一段很熟悉的代碼,我們在文章開頭已經將其展現出來了。
根據statementType的類型來判斷是哪一種StatementHandler的實現,並且RoutingStatementHandler維護了一個delegate對象,通過delegate對象來實現對實際Handler對象的調用。這裏涉及到了一個對象MappedStatement。
- SimpleExecutor
衆所周知,Excutor是主要負責執行對數據庫的操作主要執行者,經歷上面分析StatementHandler對象的創建過程,下面繼續迴歸到SimpleExectuor中;
獲取到StatementHandler之後,首先進入prepareStatement方法,該方法就是爲了獲取Statement對象,並設置Statement對象中的參數:
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;
}
- BaseStatementHandler
prepare方法負責生成Statement實例對象
@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;
} catch (SQLException e) {
closeStatement(statement);
throw e;
} catch (Exception e) {
closeStatement(statement);
throw new ExecutorException("Error preparing statement. Cause: " + e, e);
}
}
- prepareStatementHandler
parameterize方法用於處理Statement實例對應的參數。此處我們跟進下去,便可以瞭解ParameterHandler是如何解析參數的過程。
@Override
public void parameterize(Statement statement) throws SQLException {
parameterHandler.setParameters((PreparedStatement) statement);
}
- ParameterHandler
此接口只有一個默認實現類DefaultParameterHandler,跟進setParameters方法
@Override
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);
} else if (parameterObject == null) {
// 如果不是 additionalParameters 中的key,而且傳入參數是 null, 則value 就是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);
}
}
}
}
}
- SampleExecutor
執行完成參數的解析,繼續迴歸到SampleExecutor#doQuery()方法的流程中
@Override
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();
//Configuration中獲取StatementHandler
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
後面再次調用query方法,即SampleStatementHandler,PrepareStatementHandler,CallableStatementHandler中方法執行結果實體類綁定,這個具體過程解析,後面計劃專門有文章總結。
總結
經歷上面的跟蹤源碼,我們可以瞭解到StatementHandler對象具體的創建過程,以及參數和結果綁定的流程。
新手跟蹤源碼,若存在錯誤或者不足之處,希望大佬及時指正!最後,希望大家多多支持,轉發,點贊,關注,謝謝。