我們在上一章介紹到,Mybatis會將所有數據庫操作轉換成iBatis編程模型,通過門面類SqlSession來操作數據庫,但是我們深入SqlSession源碼我們會發現,SqlSession啥都沒幹,它將數據庫操作都委託給你了Excuter,如圖:
Excuter框架類圖
BaseExecutor
在BaseExecutor定義了Executor的基本實現,如查詢一級緩存,事務處理等不變的部分,操作數據庫等變化部分由子類實現,使用了模板設計模式,下面我們來看下查詢方法的源碼:
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameter);
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
/**
* 所有的查詢操作最後都是由該方法來處理的
*/
@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.");
}
if (queryStack == 0 && ms.isFlushCacheRequired()) {
// 清空本地緩存
clearLocalCache();
}
List<E> list;
try {
// 查詢層次加一
queryStack++;
// 查詢一級緩存
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
// 處理存儲過程的OUT參數
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;
}
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;
}
queryFromDatabase() 方法中,我們可以看到doQuery使用的是模板方法,具體邏輯是由子類來實現的,這樣做的好處是,子類只關心程序變化的部分,其他不變的部分由父類實現。提高了代碼的複用性和代碼的擴展性。
SimpleExecutor
普通的執行器,Mybatis的默認使用該執行器,每次新建Statement。我們還是來看下查詢方法的源碼:
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
// 獲取Mybatis配置類
Configuration configuration = ms.getConfiguration();
// 根據配置類獲取StatementHandler
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
// 創建Statement
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
// 獲取Connection連接
Connection connection = getConnection(statementLog);
// 根據Connection獲取Statement
stmt = handler.prepare(connection, transaction.getTimeout());
// 設置參數
handler.parameterize(stmt);
return stmt;
}
通過
stmt = handler.prepare(connection, transaction.getTimeout());
方法我們可以看出每次是新建Statement
。
ReuseExecutor
可以重用的執行器,複用的是Statement,內部以sql語句爲key使用一個Map將Statement對象緩存起來,只要連接不斷開,那麼Statement就可以重用。
因爲每一個新的SqlSession都有一個新的Executor對象,所以我們緩存在ReuseExecutor上的Statement的作用域是同一個SqlSession,所以其實這個緩存用處其實並不大。我們直接看下獲取Statement
源碼,其他部分和SimpleExecutor
查詢方法一樣。
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
BoundSql boundSql = handler.getBoundSql();
String sql = boundSql.getSql();
if (hasStatementFor(sql)) {
// 獲取複用的Statement
stmt = getStatement(sql);
applyTransactionTimeout(stmt);
} else {
// 新建Statement,並緩存
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());
putStatement(sql, stmt);
}
handler.parameterize(stmt);
return stmt;
}
private boolean hasStatementFor(String sql) {
try {
// 根據sql判斷是否緩存了Statement,並判斷Connection是否關閉
return statementMap.keySet().contains(sql) && !statementMap.get(sql).getConnection().isClosed();
} catch (SQLException e) {
return false;
}
}
private Statement getStatement(String s) {
return statementMap.get(s);
}
private void putStatement(String sql, Statement stmt) {
statementMap.put(sql, stmt);
}
BatchExecutor
批處理執行器,通過封裝jdbc的 statement.addBatch(String sql) 以及 statement.executeBatch(); 來實現的批處理。該執行器的事務只能是手動提交模式。
我們平時執行批量的處理是一般還可以使用sql拼接的方式。
執行批量更新時建議一次不要更新太多數據,如果更新數據量比較大時可以分段執行。
CachingExecutor
如果開啓了二級緩存那麼Mybatis會使用CachingExecutor
執行器,CachingExecutor
使用了裝飾器模式。
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
// 獲取二級緩存
Cache cache = ms.getCache();
if (cache != null) {
// 刷新緩存
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
// 查緩存
@SuppressWarnings("unchecked")
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
// 調用被裝飾則的方法
list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
// 將數據放入緩存
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
// 沒找到緩存,直接調用被裝飾則的方法
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
通過源碼我們發現,整個查詢流程變成 了 L2 -> L1 -> DB。CachingExecutor
的查詢流程,增加了二級緩存的查詢操作。
我們在實際使用緩存過程中一般很少使用Mybatis的二級緩存,如果想做二級緩存,建議直接在service層面使用第三方緩存框架,推薦使用爲監控而生的多級緩存框架 layering-cache,使用更方便靈活,查詢流程是 L1 -> L2 -> DB。
總結
- BaseExecutor:使用了模板方法模式,定義了Executor的基本實現,它是一個抽象類,不能直接對外提供服務。
- SimpleExecutor:普通的執行器,Mybatis的默認使用該執行器,每次新建Statement。
- ReuseExecutor:可以重用Statement的執行器,但是這個Statement緩存只在一次SqlSession中有效,我們平時生少有在一次SqlSession中進行多次一樣的查詢操作,所以性能提升並不大。
- BatchExecutor:批處理執行器
- CachingExecutor:二級緩存執行器,使用裝飾器模式,整個查詢流程變成 了 L2 -> L1 -> DB。建議直接使用第三方緩存框架,如:爲監控而生的多級緩存框架 layering-cache。