我們?cè)谏弦徽陆榻B到咱枉,Mybatis會(huì)將所有數(shù)據(jù)庫操作轉(zhuǎn)換成iBatis編程模型,通過門面類SqlSession來操作數(shù)據(jù)庫,但是我們深入SqlSession源碼我們會(huì)發(fā)現(xiàn),SqlSession啥都沒干,它將數(shù)據(jù)庫操作都委托給你了Excuter斩萌,如圖:
Excuter框架類圖
BaseExecutor
在BaseExecutor定義了Executor的基本實(shí)現(xiàn)缝裤,如查詢一級(jí)緩存,事務(wù)處理等不變的部分颊郎,操作數(shù)據(jù)庫等變化部分由子類實(shí)現(xiàn)憋飞,使用了模板設(shè)計(jì)模式,下面我們來看下查詢方法的源碼:
@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++;
// 查詢一級(jí)緩存
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
// 處理存儲(chǔ)過程的OUT參數(shù)
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
// 緩存未命中姆吭,查詢數(shù)據(jù)庫
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);
}
// 將查詢結(jié)果添加到本地緩存
localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
// 如果是存儲(chǔ)過程則榛做,緩存參數(shù)
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
queryFromDatabase() 方法中,我們可以看到doQuery使用的是模板方法内狸,具體邏輯是由子類來實(shí)現(xiàn)的检眯,這樣做的好處是,子類只關(guān)心程序變化的部分昆淡,其他不變的部分由父類實(shí)現(xiàn)锰瘸。提高了代碼的復(fù)用性和代碼的擴(kuò)展性。
SimpleExecutor
普通的執(zhí)行器昂灵,Mybatis的默認(rèn)使用該執(zhí)行器避凝,每次新建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();
// 根據(jù)配置類獲取StatementHandler
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
// 創(chuàng)建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);
// 根據(jù)Connection獲取Statement
stmt = handler.prepare(connection, transaction.getTimeout());
// 設(shè)置參數(shù)
handler.parameterize(stmt);
return stmt;
}
通過
stmt = handler.prepare(connection, transaction.getTimeout());
方法我們可以看出每次是新建Statement
。
ReuseExecutor
可以重用的執(zhí)行器管削,復(fù)用的是Statement倒脓,內(nèi)部以sql語句為key使用一個(gè)Map將Statement對(duì)象緩存起來,只要連接不斷開含思,那么Statement就可以重用崎弃。
因?yàn)槊恳粋€(gè)新的SqlSession都有一個(gè)新的Executor對(duì)象,所以我們緩存在ReuseExecutor上的Statement的作用域是同一個(gè)SqlSession茸俭,所以其實(shí)這個(gè)緩存用處其實(shí)并不大吊履。我們直接看下獲取Statement
源碼,其他部分和SimpleExecutor
查詢方法一樣调鬓。
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
BoundSql boundSql = handler.getBoundSql();
String sql = boundSql.getSql();
if (hasStatementFor(sql)) {
// 獲取復(fù)用的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 {
// 根據(jù)sql判斷是否緩存了Statement,并判斷Connection是否關(guān)閉
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
批處理執(zhí)行器腾窝,通過封裝jdbc的 statement.addBatch(String sql) 以及 statement.executeBatch(); 來實(shí)現(xiàn)的批處理缀踪。該執(zhí)行器的事務(wù)只能是手動(dòng)提交模式。
我們平時(shí)執(zhí)行批量的處理是一般還可以使用sql拼接的方式虹脯。
執(zhí)行批量更新時(shí)建議一次不要更新太多數(shù)據(jù)驴娃,如果更新數(shù)據(jù)量比較大時(shí)可以分段執(zhí)行。
CachingExecutor
如果開啟了二級(jí)緩存那么Mybatis會(huì)使用CachingExecutor
執(zhí)行器循集,CachingExecutor
使用了裝飾器模式唇敞。
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
// 獲取二級(jí)緩存
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) {
// 調(diào)用被裝飾則的方法
list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
// 將數(shù)據(jù)放入緩存
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
// 沒找到緩存,直接調(diào)用被裝飾則的方法
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
通過源碼我們發(fā)現(xiàn)咒彤,整個(gè)查詢流程變成 了 L2 -> L1 -> DB疆柔。CachingExecutor
的查詢流程,增加了二級(jí)緩存的查詢操作镶柱。
我們?cè)趯?shí)際使用緩存過程中一般很少使用Mybatis的二級(jí)緩存旷档,如果想做二級(jí)緩存,建議直接在service層面使用第三方緩存框架歇拆,推薦使用為監(jiān)控而生的多級(jí)緩存框架 layering-cache鞋屈,使用更方便靈活,查詢流程是 L1 -> L2 -> DB故觅。
總結(jié)
- BaseExecutor:使用了模板方法模式厂庇,定義了Executor的基本實(shí)現(xiàn),它是一個(gè)抽象類输吏,不能直接對(duì)外提供服務(wù)宋列。
- SimpleExecutor:普通的執(zhí)行器,Mybatis的默認(rèn)使用該執(zhí)行器评也,每次新建Statement炼杖。
- ReuseExecutor:可以重用Statement的執(zhí)行器灭返,但是這個(gè)Statement緩存只在一次SqlSession中有效,我們平時(shí)生少有在一次SqlSession中進(jìn)行多次一樣的查詢操作坤邪,所以性能提升并不大熙含。
- BatchExecutor:批處理執(zhí)行器
- CachingExecutor:二級(jí)緩存執(zhí)行器,使用裝飾器模式艇纺,整個(gè)查詢流程變成 了 L2 -> L1 -> DB怎静。建議直接使用第三方緩存框架,如:為監(jiān)控而生的多級(jí)緩存框架 layering-cache黔衡。