執(zhí)行器 Executor
是 MyBatis
的核心接口之一抒巢,接口層提供的相關(guān)數(shù)據(jù)庫操作键闺,都是基于 Executor
的子類實現(xiàn)的慢宗。
創(chuàng)建執(zhí)行器
在創(chuàng)建 sql
會話時再来,MyBatis
會調(diào)用 Configuration#newExecutor
方法創(chuàng)建執(zhí)行器。枚舉類 ExecutorType
定義了三種執(zhí)行器類型闻鉴,即 SIMPLE
茵乱、REUSE
和 Batch
,這些執(zhí)行器的主要區(qū)別在于:
-
SIMPLE
在每次執(zhí)行完成后都會關(guān)閉statement
對象孟岛; -
REUSE
會在本地維護(hù)一個容器瓶竭,當(dāng)前statement
創(chuàng)建完成后放入容器中,當(dāng)下次執(zhí)行相同的sql
時會復(fù)用statement
對象渠羞,執(zhí)行完畢后也不會關(guān)閉斤贰; -
BATCH
會將修改操作記錄在本地,等待程序觸發(fā)或有下一次查詢時才批量執(zhí)行修改操作次询。
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
// 默認(rèn)類型為 simple
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
if (cacheEnabled) {
// 如果全局緩存打開荧恍,使用 CachingExecutor 代理執(zhí)行器
executor = new CachingExecutor(executor);
}
// 應(yīng)用插件
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
執(zhí)行器創(chuàng)建后,如果全局緩存配置是有效的,則會將執(zhí)行器裝飾為 CachingExecutor
送巡。
基礎(chǔ)執(zhí)行器
SimpleExecutor
摹菠、ReuseExecutor
、BatchExecutor
均繼承自 BaseExecutor
骗爆。BaseExecutor
實現(xiàn)了 Executor
的全部方法次氨,對緩存、事務(wù)摘投、連接處理等提供了一些模板方法煮寡,但是針對具體的數(shù)據(jù)庫操作留下了四個抽象方法交由子類實現(xiàn)。
/**
* 更新
*/
protected abstract int doUpdate(MappedStatement ms, Object parameter)
throws SQLException;
/**
* 刷新 statement
*/
protected abstract List<BatchResult> doFlushStatements(boolean isRollback)
throws SQLException;
/**
* 查詢
*/
protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
throws SQLException;
/**
* 查詢獲取游標(biāo)對象
*/
protected abstract <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql)
throws SQLException;
基礎(chǔ)執(zhí)行器的查詢邏輯如下:
@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()) {
// 非嵌套查詢且設(shè)置強制刷新時清除緩存
clearLocalCache();
}
List<E> list;
try {
queryStack++;
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
// 緩存不為空谷朝,組裝存儲過程出參
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
// 無本地緩存洲押,執(zhí)行數(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;
}
/**
* 查詢本地緩存,組裝存儲過程結(jié)果集
*/
private void handleLocallyCachedOutputParameters(MappedStatement ms, CacheKey key, Object parameter, BoundSql boundSql) {
if (ms.getStatementType() == StatementType.CALLABLE) {
// 存儲過程類型圆凰,查詢緩存
final Object cachedParameter = localOutputParameterCache.getObject(key);
if (cachedParameter != null && parameter != null) {
final MetaObject metaCachedParameter = configuration.newMetaObject(cachedParameter);
final MetaObject metaParameter = configuration.newMetaObject(parameter);
for (ParameterMapping parameterMapping : boundSql.getParameterMappings()) {
if (parameterMapping.getMode() != ParameterMode.IN) {
// 參數(shù)類型為 OUT 或 INOUT 的杈帐,組裝結(jié)果集
final String parameterName = parameterMapping.getProperty();
final Object cachedValue = metaCachedParameter.getValue(parameterName);
metaParameter.setValue(parameterName, cachedValue);
}
}
}
}
}
/**
* 查詢數(shù)據(jù)庫獲取結(jié)果集
*/
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
// 放一個 placeHolder 標(biāo)志
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
// 執(zhí)行查詢
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
localCache.removeObject(key);
}
// 查詢結(jié)果集放入本地緩存
localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
// 如果是存儲過程查詢,將存儲過程結(jié)果集放入本地緩存
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
執(zhí)行查詢時 MyBatis
首先會根據(jù) CacheKey
查詢本地緩存专钉,CacheKey
由本次查詢的參數(shù)生成挑童,本地緩存由 PerpetualCache
實現(xiàn),這就是 MyBatis
的一級緩存跃须。一級緩存維護(hù)對象 localCache
是基礎(chǔ)執(zhí)行器的本地變量站叼,因此只有相同 sql
會話的查詢才能共享一級緩存。當(dāng)一級緩存中沒有對應(yīng)的數(shù)據(jù)菇民,基礎(chǔ)執(zhí)行器最終會調(diào)用 doQuery
方法交由子類去獲取數(shù)據(jù)尽楔。
而執(zhí)行 update
等其它操作時,則會首先清除本地的一級緩存再交由子類執(zhí)行具體的操作:
@Override
public int update(MappedStatement ms, Object parameter) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
// 清空本地緩存
clearLocalCache();
// 調(diào)用子類執(zhí)行器邏輯
return doUpdate(ms, parameter);
}
簡單執(zhí)行器
簡單執(zhí)行器是 MyBatis
的默認(rèn)執(zhí)行器第练。其封裝了對 JDBC
的操作阔馋,對于查詢方法 doQuery
的實現(xiàn)如下,其主要包括創(chuàng)建 statement
處理器娇掏、創(chuàng)建 statement
呕寝、執(zhí)行查詢、關(guān)閉 statement
婴梧。
@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();
// 創(chuàng)建 statement 處理器
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
// 創(chuàng)建 statement
stmt = prepareStatement(handler, ms.getStatementLog());
// 執(zhí)行查詢
return handler.query(stmt, resultHandler);
} finally {
// 關(guān)閉 statement
closeStatement(stmt);
}
}
創(chuàng)建 statement 處理器
全局配置類 Configuration
提供了方法 newStatementHandler
用于創(chuàng)建 statement
處理器:
/**
* 創(chuàng)建 statement 處理器
*/
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
// StatementHandler 包裝對象下梢,根據(jù) statement 類型創(chuàng)建代理處理器,并將實際操作委托給代理處理器處理
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
// 應(yīng)用插件
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
實際每次創(chuàng)建的 statement
處理器對象都是由 RoutingStatementHandler
創(chuàng)建的塞蹭,RoutingStatementHandler
根據(jù)當(dāng)前 MappedStatement
的類型創(chuàng)建具體的 statement
類型處理器孽江。StatementType
定義了 3
個 statement
類型枚舉,分別對應(yīng) JDBC
的普通語句浮还、預(yù)編譯語句和存儲過程語句竟坛。
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
// 根據(jù) statement 類型選擇對應(yīng)的 statementHandler
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());
}
}
創(chuàng)建 statement
簡單執(zhí)行器中的 Statement
對象是根據(jù)上述步驟中生成的 statement
處理器獲取的。
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
// 獲取代理連接對象
Connection connection = getConnection(statementLog);
// 創(chuàng)建 statement 對象
stmt = handler.prepare(connection, transaction.getTimeout());
// 設(shè)置 statement 參數(shù)
handler.parameterize(stmt);
return stmt;
}
@Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
ErrorContext.instance().sql(boundSql.getSql());
Statement statement = null;
try {
// 從連接中創(chuàng)建 statement 對象
statement = instantiateStatement(connection);
// 設(shè)置超時時間
setStatementTimeout(statement, transactionTimeout);
// 設(shè)置分批獲取數(shù)據(jù)數(shù)量
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);
}
}
執(zhí)行查詢
創(chuàng)建 statement
對象完成即可通過 JDBC
的 API
執(zhí)行數(shù)據(jù)庫查詢钧舌,并從 statement
對象中獲取查詢結(jié)果担汤,根據(jù)配置進(jìn)行轉(zhuǎn)換。
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
// 執(zhí)行查詢
ps.execute();
// 處理結(jié)果集
return resultSetHandler.handleResultSets(ps);
}
/**
* 處理結(jié)果集
*/
@Override
public List<Object> handleResultSets(Statement stmt) throws SQLException {
ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
// 多結(jié)果集
final List<Object> multipleResults = new ArrayList<>();
int resultSetCount = 0;
ResultSetWrapper rsw = getFirstResultSet(stmt);
// statement 對應(yīng)的所有 ResultMap 對象
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
// 驗證結(jié)果集不為空時洼冻,ResultMap 數(shù)量不能為 0
validateResultMapsCount(rsw, resultMapCount);
while (rsw != null && resultMapCount > resultSetCount) {
// 逐個獲取 ResultMap
ResultMap resultMap = resultMaps.get(resultSetCount);
// 轉(zhuǎn)換結(jié)果集崭歧,放到 multipleResults 容器中
handleResultSet(rsw, resultMap, multipleResults, null);
// 獲取下一個待處理的結(jié)果集
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
// statement 配置的多結(jié)果集類型
String[] resultSets = mappedStatement.getResultSets();
if (resultSets != null) {
while (rsw != null && resultSetCount < resultSets.length) {
ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
if (parentMapping != null) {
String nestedResultMapId = parentMapping.getNestedResultMapId();
ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
handleResultSet(rsw, resultMap, null, parentMapping);
}
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
}
return collapseSingleResultList(multipleResults);
}
關(guān)閉連接
查詢完成后 statement
對象會被關(guān)閉。
/**
* 關(guān)閉 statement
*
* @param statement
*/
protected void closeStatement(Statement statement) {
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
// ignore
}
}
}
簡單執(zhí)行器中的其它數(shù)據(jù)庫執(zhí)行方法與 doQuery
方法實現(xiàn)類似撞牢。
復(fù)用執(zhí)行器
ReuseExecutor
相對于 SimpleExecutor
實現(xiàn)了對 statment
對象的復(fù)用率碾,其在本地維護(hù)了 statementMap
用于保存 sql
語句和 statement
對象的關(guān)系。當(dāng)調(diào)用 prepareStatement
方法獲取 statement
對象時首先會查找本地是否有對應(yīng)的 statement
對象屋彪,如果有則進(jìn)行復(fù)用所宰,負(fù)責(zé)重新創(chuàng)建并將 statement
對象放入本地緩存。
/**
* 創(chuàng)建 statement 對象
*
* @param handler
* @param statementLog
* @return
* @throws SQLException
*/
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
BoundSql boundSql = handler.getBoundSql();
String sql = boundSql.getSql();
if (hasStatementFor(sql)) {
// 如果本地容器中包含當(dāng)前 sql 對應(yīng)的 statement 對象畜挥,進(jìn)行復(fù)用
stmt = getStatement(sql);
applyTransactionTimeout(stmt);
} else {
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());
putStatement(sql, stmt);
}
handler.parameterize(stmt);
return stmt;
}
private boolean hasStatementFor(String sql) {
try {
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);
}
提交或回滾會導(dǎo)致執(zhí)行器調(diào)用 doFlushStatements
方法仔粥,復(fù)用執(zhí)行器會因此批量關(guān)閉本地的 statement
對象。
/**
* 批量關(guān)閉 statement 對象
*
* @param isRollback
* @return
*/
@Override
public List<BatchResult> doFlushStatements(boolean isRollback) {
for (Statement stmt : statementMap.values()) {
closeStatement(stmt);
}
statementMap.clear();
return Collections.emptyList();
}
批量執(zhí)行器
BatchExecutor
相對于 SimpleExecutor
蟹但,其 update
操作是批量執(zhí)行的。
@Override
public int doUpdate(MappedStatement ms, Object parameterObject) throws SQLException {
final Configuration configuration = ms.getConfiguration();
final StatementHandler handler = configuration.newStatementHandler(this, ms, parameterObject, RowBounds.DEFAULT, null, null);
final BoundSql boundSql = handler.getBoundSql();
final String sql = boundSql.getSql();
final Statement stmt;
if (sql.equals(currentSql) && ms.equals(currentStatement)) {
// 如果當(dāng)前 sql 與上次傳入 sql 相同且為相同的 MappedStatement,復(fù)用 statement 對象
int last = statementList.size() - 1;
// 獲取最后一個 statement 對象
stmt = statementList.get(last);
// 設(shè)置超時時間
applyTransactionTimeout(stmt);
// 設(shè)置參數(shù)
handler.parameterize(stmt);//fix Issues 322
// 獲取批量執(zhí)行結(jié)果對象
BatchResult batchResult = batchResultList.get(last);
batchResult.addParameterObject(parameterObject);
} else {
// 創(chuàng)建新的 statement 對象
Connection connection = getConnection(ms.getStatementLog());
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt); //fix Issues 322
currentSql = sql;
currentStatement = ms;
statementList.add(stmt);
batchResultList.add(new BatchResult(ms, sql, parameterObject));
}
// 執(zhí)行 JDBC 批量添加 sql 語句操作
handler.batch(stmt);
return BATCH_UPDATE_RETURN_VALUE;
}
/**
* 批量執(zhí)行 sql
*
* @param isRollback
* @return
* @throws SQLException
*/
@Override
public List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException {
try {
// 批量執(zhí)行結(jié)果
List<BatchResult> results = new ArrayList<>();
if (isRollback) {
return Collections.emptyList();
}
for (int i = 0, n = statementList.size(); i < n; i++) {
Statement stmt = statementList.get(i);
applyTransactionTimeout(stmt);
BatchResult batchResult = batchResultList.get(i);
try {
// 設(shè)置執(zhí)行影響行數(shù)
batchResult.setUpdateCounts(stmt.executeBatch());
MappedStatement ms = batchResult.getMappedStatement();
List<Object> parameterObjects = batchResult.getParameterObjects();
KeyGenerator keyGenerator = ms.getKeyGenerator();
if (Jdbc3KeyGenerator.class.equals(keyGenerator.getClass())) {
Jdbc3KeyGenerator jdbc3KeyGenerator = (Jdbc3KeyGenerator) keyGenerator;
// 設(shè)置數(shù)據(jù)庫生成的主鍵
jdbc3KeyGenerator.processBatch(ms, stmt, parameterObjects);
} else if (!NoKeyGenerator.class.equals(keyGenerator.getClass())) { //issue #141
for (Object parameter : parameterObjects) {
keyGenerator.processAfter(this, ms, stmt, parameter);
}
}
// Close statement to close cursor #1109
closeStatement(stmt);
} catch (BatchUpdateException e) {
StringBuilder message = new StringBuilder();
message.append(batchResult.getMappedStatement().getId())
.append(" (batch index #")
.append(i + 1)
.append(")")
.append(" failed.");
if (i > 0) {
message.append(" ")
.append(i)
.append(" prior sub executor(s) completed successfully, but will be rolled back.");
}
throw new BatchExecutorException(message.toString(), e, results, batchResult);
}
results.add(batchResult);
}
return results;
} finally {
for (Statement stmt : statementList) {
closeStatement(stmt);
}
currentSql = null;
statementList.clear();
batchResultList.clear();
}
}
執(zhí)行器提交或回滾事務(wù)時會調(diào)用 doFlushStatements
哄酝,從而批量執(zhí)行提交的 sql
語句并最終批量關(guān)閉 statement
對象瓢棒。
緩存執(zhí)行器與二級緩存
CachingExecutor
對基礎(chǔ)執(zhí)行器進(jìn)行了裝飾,其作用就是為查詢提供二級緩存客叉。所謂的二級緩存是由 CachingExecutor
維護(hù)的诵竭,相對默認(rèn)內(nèi)置的一級緩存而言的緩存。二者區(qū)別如下:
- 一級緩存由基礎(chǔ)執(zhí)行器維護(hù)兼搏,且不可關(guān)閉卵慰。二級緩存的配置是開發(fā)者可干預(yù)的,在
xml
文件或注解中針對namespace
的緩存配置就是二級緩存配置向族。 - 一級緩存在執(zhí)行器中維護(hù)呵燕,即不同
sql
會話不能共享一級緩存。二級緩存則是根據(jù)namespace
維護(hù)件相,不同sql
會話是可以共享二級緩存的再扭。
CachingExecutor
中的方法大多是通過直接調(diào)用其代理的執(zhí)行器來實現(xiàn)的,而查詢操作則會先查詢二級緩存夜矗。
/**
* 緩存事務(wù)管理器
*/
private final TransactionalCacheManager tcm = new TransactionalCacheManager();
@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) {
// 當(dāng)前 statement 配置使用二級緩存
ensureNoOutParams(ms, boundSql);
@SuppressWarnings("unchecked")
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
// 二級緩存中沒用數(shù)據(jù)泛范,調(diào)用代理執(zhí)行器
list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
// 將查詢結(jié)果放入二級緩存
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
// 無二級緩存配置,調(diào)用代理執(zhí)行器獲取結(jié)果
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
private void flushCacheIfRequired(MappedStatement ms) {
Cache cache = ms.getCache();
if (cache != null && ms.isFlushCacheRequired()) {
// 存在 namespace 對應(yīng)的緩存配置紊撕,且當(dāng)前 statement 配置了刷新緩存罢荡,執(zhí)行清空緩存操作
// 非 select 語句配置了默認(rèn)刷新
tcm.clear(cache);
}
}
如果對應(yīng)的 statement
的二級緩存配置有效,則會先通過緩存事務(wù)管理器 TransactionalCacheManager
查詢二級緩存,如果沒有命中則查詢一級緩存区赵,仍沒有命中才會執(zhí)行數(shù)據(jù)庫查詢惭缰。
緩存事務(wù)管理器
緩存執(zhí)行器對二級緩存的維護(hù)是基于緩存事務(wù)管理器 TransactionalCacheManager
的,其內(nèi)部維護(hù)了一個 Map
容器笼才,用于保存 namespace
緩存配置與事務(wù)緩存對象的映射關(guān)系漱受。
public class TransactionalCacheManager {
/**
* 緩存配置 - 緩存事務(wù)對象
*/
private final Map<Cache, TransactionalCache> transactionalCaches = new HashMap<>();
/**
* 清除緩存
*
* @param cache
*/
public void clear(Cache cache) {
getTransactionalCache(cache).clear();
}
/**
* 獲取緩存
*/
public Object getObject(Cache cache, CacheKey key) {
return getTransactionalCache(cache).getObject(key);
}
/**
* 寫緩存
*/
public void putObject(Cache cache, CacheKey key, Object value) {
getTransactionalCache(cache).putObject(key, value);
}
/**
* 緩存提交
*/
public void commit() {
for (TransactionalCache txCache : transactionalCaches.values()) {
txCache.commit();
}
}
/**
* 緩存回滾
*/
public void rollback() {
for (TransactionalCache txCache : transactionalCaches.values()) {
txCache.rollback();
}
}
/**
* 獲取或新建事務(wù)緩存對象
*/
private TransactionalCache getTransactionalCache(Cache cache) {
return transactionalCaches.computeIfAbsent(cache, TransactionalCache::new);
}
}
緩存配置映射的事務(wù)緩存對象就是前文中提到過的事務(wù)緩存裝飾器 TransactionalCache
。getTransactionalCache
會從維護(hù)容器中查找對應(yīng)的事務(wù)緩存對象骡送,如果找不到就創(chuàng)建一個事務(wù)緩存對象昂羡,即通過事務(wù)緩存對象裝飾當(dāng)前緩存配置。
查詢緩存時摔踱,如果緩存未命中虐先,則將對應(yīng)的 key
放入未命中隊列,執(zhí)行數(shù)據(jù)庫查詢完畢后寫緩存時并不是立刻寫到緩存配置的本地容器中派敷,而是暫時放入待提交隊列中蛹批,當(dāng)觸發(fā)事務(wù)提交時才將提交隊列中的緩存數(shù)據(jù)寫到緩存配置中。如果發(fā)生回滾膀息,則提交隊列中的數(shù)據(jù)會被清空般眉,從而保證了數(shù)據(jù)的一致性。
@Override
public Object getObject(Object key) {
// issue #116
Object object = delegate.getObject(key);
if (object == null) {
// 放入未命中緩存的 key 的隊列
entriesMissedInCache.add(key);
}
// issue #146
if (clearOnCommit) {
return null;
} else {
return object;
}
}
@Override
public void putObject(Object key, Object object) {
// 緩存先寫入待提交容器
entriesToAddOnCommit.put(key, object);
}
/**
* 事務(wù)提交
*/
public void commit() {
if (clearOnCommit) {
delegate.clear();
}
// 提交緩存
flushPendingEntries();
reset();
}
/**
* 事務(wù)回滾
*/
public void rollback() {
unlockMissedEntries();
reset();
}
/**
* 事務(wù)提交潜支,提交待提交的緩存甸赃。
*/
private void flushPendingEntries() {
for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) {
delegate.putObject(entry.getKey(), entry.getValue());
}
for (Object entry : entriesMissedInCache) {
if (!entriesToAddOnCommit.containsKey(entry)) {
delegate.putObject(entry, null);
}
}
}
/**
* 事務(wù)回滾,清理未命中緩存冗酿。
*/
private void unlockMissedEntries() {
for (Object entry : entriesMissedInCache) {
try {
delegate.removeObject(entry);
} catch (Exception e) {
log.warn("Unexpected exception while notifiying a rollback to the cache adapter."
+ "Consider upgrading your cache adapter to the latest version. Cause: " + e);
}
}
}
二級緩存與一級緩存的互斥性
使用二級緩存要求無論是否配置了事務(wù)自動提交埠对,在執(zhí)行完成后, sql
會話必須手動提交事務(wù)才能觸發(fā)事務(wù)緩存管理器維護(hù)緩存到緩存配置中裁替,否則二級緩存無法生效项玛。而緩存執(zhí)行器在觸發(fā)事務(wù)提交時,不僅會調(diào)用事務(wù)緩存管理器提交弱判,還會調(diào)用代理執(zhí)行器提交事務(wù):
@Override
public void commit(boolean required) throws SQLException {
// 代理執(zhí)行器提交
delegate.commit(required);
// 事務(wù)緩存管理器提交
tcm.commit();
}
代理執(zhí)行器的事務(wù)提交方法繼承自 BaseExecutor
襟沮,其 commit
方法中調(diào)用了 clearLocalCache
方法清除本地一級緩存。因此二級緩存和一級緩存的使用是互斥的昌腰。
@Override
public void commit(boolean required) throws SQLException {
if (closed) {
throw new ExecutorException("Cannot commit, transaction is already closed");
}
// 清除本地一級緩存
clearLocalCache();
flushStatements();
if (required) {
transaction.commit();
}
}
小結(jié)
MyBatis
提供若干執(zhí)行器封裝底層 JDBC
操作和結(jié)果集轉(zhuǎn)換开伏,并嵌入 sql
會話維度的一級緩存和 namespace
維度的二級緩存。接口層可以通過調(diào)用不同類型的執(zhí)行器來完成 sql
相關(guān)操作遭商。
-
org.apache.ibatis.executor.Executor
:數(shù)據(jù)庫操作執(zhí)行器抽象接口固灵。 -
org.apache.ibatis.executor.BaseExecutor
:執(zhí)行器基礎(chǔ)抽象實現(xiàn)。 -
org.apache.ibatis.executor.SimpleExecutor
:簡單類型執(zhí)行器劫流。 -
org.apache.ibatis.executor.ReuseExecutor
:statement
復(fù)用執(zhí)行器巫玻。 -
org.apache.ibatis.executor.BatchExecutor
:批量執(zhí)行器丛忆。 -
org.apache.ibatis.executor.CachingExecutor
:二級緩存執(zhí)行器。 -
org.apache.ibatis.executor.statement.StatementHandler
:statement
處理器抽象接口仍秤。 -
org.apache.ibatis.executor.statement.BaseStatementHandler
:statement
處理器基礎(chǔ)抽象實現(xiàn)熄诡。 -
org.apache.ibatis.executor.statement.RoutingStatementHandler
:statement
處理器路由對象。 -
org.apache.ibatis.executor.statement.SimpleStatementHandler
:簡單statement
處理器徒扶。 -
org.apache.ibatis.executor.statement.PreparedStatementHandler
:預(yù)編譯statement
處理器粮彤。 -
org.apache.ibatis.executor.statement.CallableStatementHandler
:存儲過程statement
處理器根穷。 -
org.apache.ibatis.cache.TransactionalCacheManager
:緩存事務(wù)管理器姜骡。