Mybatis源码阅读(四):核心接口4.2——Executor(上)
*************************************優(yōu)雅的分割線 **********************************
分享一波:程序員賺外快-必看的巔峰干貨
如果以上內(nèi)容對(duì)你覺(jué)得有用,并想獲取更多的賺錢方式和免費(fèi)的技術(shù)教程
請(qǐng)關(guān)注微信公眾號(hào):HB荷包
一個(gè)能讓你學(xué)習(xí)技術(shù)和賺錢方法的公眾號(hào),持續(xù)更新
*************************************優(yōu)雅的分割線 **********************************
Executor
Executor是Mybatis的核心接口之一,其中定義了數(shù)據(jù)庫(kù)操作的基本方法。在實(shí)際應(yīng)用中涉及的SqlSession的操作都是基于Executor實(shí)現(xiàn)的。Executor代碼如下。
/**
-
Mybatis的核心接口,定義了操作數(shù)據(jù)庫(kù)的方法
-
SqlSession接口的功能都是基于Executor實(shí)現(xiàn)的
-
@author Clinton Begin
*/
public interface Executor {ResultHandler NO_RESULT_HANDLER = null;
/**
- 執(zhí)行update、insert、delete語(yǔ)句
- @param ms
- @param parameter
- @return
- @throws SQLException
*/
int update(MappedStatement ms, Object parameter) throws SQLException;
/**
- 執(zhí)行select
- @param ms
- @param parameter
- @param rowBounds
- @param resultHandler
- @param cacheKey
- @param boundSql
- @param
- @return
- @throws SQLException
*/
List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException;
/**
- 執(zhí)行select
- @param ms
- @param parameter
- @param rowBounds
- @param resultHandler
- @param
- @return
- @throws SQLException
*/
List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;
/**
- 執(zhí)行select,返回游標(biāo)
- @param ms
- @param parameter
- @param rowBounds
- @param
- @return
- @throws SQLException
*/
Cursor queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException;
/**
- 批量執(zhí)行SQL語(yǔ)句
- @return
- @throws SQLException
*/
List flushStatements() throws SQLException;
/**
- 提交事務(wù)
- @param required
- @throws SQLException
*/
void commit(boolean required) throws SQLException;
/**
- 回滾事務(wù)
- @param required
- @throws SQLException
*/
void rollback(boolean required) throws SQLException;
/**
- 創(chuàng)建緩存中的CacheKey對(duì)象
- @param ms
- @param parameterObject
- @param rowBounds
- @param boundSql
- @return
*/
CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql);
/**
- 根據(jù)CacheKey查找緩存是否出在
- @param ms
- @param key
- @return
*/
boolean isCached(MappedStatement ms, CacheKey key);
/**
- 清除一級(jí)緩存
*/
void clearLocalCache();
/**
- 延遲加載一級(jí)緩存中的數(shù)據(jù)
- @param ms
- @param resultObject
- @param property
- @param key
- @param targetType
*/
void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType);
/**
- 獲取事務(wù)對(duì)象
- @return
*/
Transaction getTransaction();
/**
- 關(guān)閉Executor對(duì)象
- @param forceRollback
*/
void close(boolean forceRollback);
/**
- 檢測(cè)Executor是否關(guān)閉
- @return
*/
boolean isClosed();
/**
- 設(shè)置包裝的Executor
- @param executor
*/
void setExecutorWrapper(Executor executor);
}
[點(diǎn)擊并拖拽以移動(dòng)]
Executor接口的實(shí)現(xiàn)中使用到了裝飾器模式和模板方法模式,關(guān)于設(shè)計(jì)模式的內(nèi)容可以查看我之前的文章,這里就不貼出文章鏈接了。Executor的實(shí)現(xiàn)如圖所示。
BaseExecutor
BaseExecutor是個(gè)抽象類,實(shí)現(xiàn)了Executor大部分的方法。BaseExecutor中主要提供了緩存管理和事務(wù)管理的基本功能,繼承BaseExecutor的子類只需要實(shí)現(xiàn)四個(gè)基本的方法來(lái)完成數(shù)據(jù)庫(kù)的相關(guān)操作即可,分別是doUpdate、doQuery、doQueryCursor、doFlushStatement。其余的方法在BaseExecutor中都有了實(shí)現(xiàn)。BaseExecutor的字段如下
/*** 事務(wù)對(duì)象*/ protected Transaction transaction;/*** 封裝的Executor對(duì)象*/ protected Executor wrapper;/*** 延遲加載隊(duì)列*/ protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads;/*** 一級(jí)緩存,用于緩存該Executor對(duì)象查詢結(jié)果集映射得到的結(jié)果對(duì)象*/ protected PerpetualCache localCache;/*** 一級(jí)緩存,用來(lái)緩存輸出類型的參數(shù)*/ protected PerpetualCache localOutputParameterCache; protected Configuration configuration;/*** 記錄嵌套查詢的層數(shù)*/ protected int queryStack; /*** 標(biāo)識(shí)Executor是否關(guān)閉*/ private boolean closed;一級(jí)緩存
常見(jiàn)的系統(tǒng)中,數(shù)據(jù)庫(kù)資源是比較珍貴的,在web系統(tǒng)中的性能瓶頸主要也就是數(shù)據(jù)庫(kù)。在設(shè)計(jì)系統(tǒng)時(shí),會(huì)使用多種優(yōu)化手段去減少數(shù)據(jù)庫(kù)的直接訪問(wèn),比如使用緩存。使用緩存可以減少系統(tǒng)與數(shù)據(jù)庫(kù)的網(wǎng)絡(luò)交互、減少數(shù)據(jù)庫(kù)訪問(wèn)次數(shù)、降低數(shù)據(jù)庫(kù)負(fù)擔(dān)、降低重復(fù)創(chuàng)建和銷毀對(duì)象等一系列的開(kāi)銷,從而提升系統(tǒng)的性能。同時(shí),當(dāng)數(shù)據(jù)庫(kù)意外宕機(jī)時(shí),緩存中保存的數(shù)據(jù)可以繼續(xù)支持系統(tǒng)部分功能的正常展示,提高系統(tǒng)的可用性。Mybatis提供了一級(jí)緩存和二級(jí)緩存,我們這里先討論一級(jí)緩存。
一級(jí)緩存是會(huì)話級(jí)別的緩存,在Mybatis中每創(chuàng)建一個(gè)SqlSession對(duì)象,就表示開(kāi)啟一次數(shù)據(jù)庫(kù)會(huì)話。在一次會(huì)話中,系統(tǒng)可能回反復(fù)的執(zhí)行相同的查詢語(yǔ)句,如果不對(duì)數(shù)據(jù)庫(kù)進(jìn)行緩存,那么短時(shí)間內(nèi)執(zhí)行多次完全相同的SQL語(yǔ)句,查詢到的結(jié)果集也可能完全相同,就造成了數(shù)據(jù)庫(kù)資源的浪費(fèi)。
為了避免這種問(wèn)題,Executor對(duì)象中會(huì)建立一個(gè)簡(jiǎn)單的緩存,也就是一級(jí)緩存。它會(huì)將每次查詢結(jié)果緩存起來(lái),再執(zhí)行查詢操作時(shí),會(huì)先查詢一級(jí)緩存,如果存在完全一樣的查詢語(yǔ)句,則直接從一級(jí)緩存中取出相應(yīng)的結(jié)果對(duì)象返回給用戶,從而減少數(shù)據(jù)庫(kù)壓力。
一級(jí)緩存的生命周期與SqlSession相同,也就與SqlSession封裝的Executor對(duì)象的生命周期相同,當(dāng)調(diào)用了Executor的close方法時(shí),該Executor中的一級(jí)緩存將會(huì)不可用。同時(shí),一級(jí)緩存中對(duì)象的存活時(shí)間也會(huì)受其他因素影響,比如在執(zhí)行update方法時(shí),也會(huì)先清空一級(jí)緩存。
query
BaseExecutor方法會(huì)首先創(chuàng)建CacheKey對(duì)象,并根據(jù)CacheKey對(duì)象查找一級(jí)緩存,如果緩存命中則直接返回緩存中記錄的結(jié)果對(duì)象。如果沒(méi)有命中則查詢數(shù)據(jù)庫(kù)得到結(jié)果集,之后將結(jié)果集映射成對(duì)象保存到一級(jí)緩存中,同時(shí)返回結(jié)果對(duì)象。query方法如下所示。
@Override public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {// 獲取BoundSql對(duì)象BoundSql boundSql = ms.getBoundSql(parameter);// 創(chuàng)建CacheKey對(duì)象CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);return query(ms, parameter, rowBounds, resultHandler, key, boundSql); }在query方法中會(huì)先獲取到boundSql對(duì)象,并且去創(chuàng)建CacheKey對(duì)象,再調(diào)用query的一個(gè)重載方法。
這里的CacheKey由MappedStatement的id、對(duì)應(yīng)的offset和limit、包含問(wèn)號(hào)的sql語(yǔ)句、用戶傳遞的實(shí)參、Environment的id五部分構(gòu)成,代碼如下。
/*** 創(chuàng)建CacheKey對(duì)象* CacheKey由Sql節(jié)點(diǎn)的id、offset、limit、sql、實(shí)參、環(huán)境組成** @param ms* @param parameterObject* @param rowBounds* @param boundSql* @return*/ @Override public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {if (closed) {throw new ExecutorException("Executor was closed.");}CacheKey cacheKey = new CacheKey();// 將sql節(jié)點(diǎn)的id添加到CacheKeycacheKey.update(ms.getId());// 將offset添加到CacheKeycacheKey.update(rowBounds.getOffset());// 將limit添加到CacheKeycacheKey.update(rowBounds.getLimit());// 將SQL添加到CacheKey(包含?的sql)cacheKey.update(boundSql.getSql());// 獲取參數(shù)映射List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();// 獲取類型處理器TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();// 遍歷參數(shù)映射for (ParameterMapping parameterMapping : parameterMappings) {// 輸出類型參數(shù)不要if (parameterMapping.getMode() != ParameterMode.OUT) {Object value;// 獲取屬性名稱String propertyName = parameterMapping.getProperty();// 獲取參數(shù)值if (boundSql.hasAdditionalParameter(propertyName)) {value = boundSql.getAdditionalParameter(propertyName);} else if (parameterObject == null) {value = null;} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {value = parameterObject;} else {MetaObject metaObject = configuration.newMetaObject(parameterObject);value = metaObject.getValue(propertyName);}// 將實(shí)參參數(shù)值添加到CacheKeycacheKey.update(value);}}// 環(huán)境不為空if (configuration.getEnvironment() != null) {// 將當(dāng)前環(huán)境添加到CacheKeycacheKey.update(configuration.getEnvironment().getId());}return cacheKey; }而query的重載方法會(huì)根據(jù)創(chuàng)建的CacheKey對(duì)象查詢一級(jí)緩存。如果緩存命中則將緩存中記錄的結(jié)果對(duì)象返回,如果未命中,則調(diào)用doQuery方法查詢數(shù)據(jù)庫(kù),并存到一級(jí)緩存。代碼如下。
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {// 存入到錯(cuò)誤上下文中,便于后面操作異常ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());if (closed) {throw new ExecutorException("Executor was closed.");}// 非嵌套查詢并且當(dāng)前select節(jié)點(diǎn)配置了flushCacheif (queryStack == 0 && ms.isFlushCacheRequired()) {// 先清空緩存clearLocalCache();}List<E> list;try {// 查詢層數(shù)+1queryStack++;// 先查詢 一級(jí)緩存list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;if (list != null) {// 針對(duì)存儲(chǔ)過(guò)程調(diào)用的處理。在一級(jí)緩存 命中時(shí),獲取緩存中保存的輸出類型參數(shù),設(shè)置到用戶傳入的實(shí)參中handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);} else {// 數(shù)據(jù)庫(kù)查詢,并得到映射后的結(jié)果對(duì)象list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);}} finally {// 當(dāng)前查詢完成,查詢層數(shù)減少queryStack--;}// 延遲加載相關(guān)if (queryStack == 0) {// 觸發(fā)DeferredLoad加載一級(jí)緩存中記錄的嵌套查詢的結(jié)果對(duì)象for (DeferredLoad deferredLoad : deferredLoads) {deferredLoad.load();}// 加載完成后清除deferredLoadsdeferredLoads.clear();if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {// 根據(jù)localCacheScope配置決定是否清空一級(jí)緩存clearLocalCache();}}return list; }BaseExecutor中緩存除了緩存結(jié)果集以外,在分析嵌套查詢時(shí),如果一級(jí)緩存中緩存了嵌套查詢的結(jié)果對(duì)象,則可以從一級(jí)緩存中直接加載該結(jié)果對(duì)象。如果一級(jí)緩存中記錄的嵌套查詢的結(jié)果對(duì)象并未完全加載,則可以通過(guò)DeferredLoad實(shí)現(xiàn)類實(shí)現(xiàn)延遲加載的功能。與這個(gè)流程相關(guān)的方法有兩個(gè),isCached方法負(fù)責(zé)檢測(cè)是否緩存了指定查詢的結(jié)果對(duì)象,deferLoad方法負(fù)責(zé)創(chuàng)建DeferredLoad對(duì)象并添加到deferredLoad集合中。代碼如下。
/*** 檢測(cè)是否緩存了指定查詢的結(jié)果對(duì)象** @param ms* @param key* @return*/ @Override public boolean isCached(MappedStatement ms, CacheKey key) {// 檢測(cè)緩存中是否花奴才能了CacheKey對(duì)象return localCache.getObject(key) != null; }/*** 負(fù)責(zé)創(chuàng)建DeferredLoad對(duì)象并將其添加到deferredLoads集合中** @param ms* @param resultObject* @param property* @param key* @param targetType*/ @Override public void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType) {if (closed) {throw new ExecutorException("Executor was closed.");}DeferredLoad deferredLoad = new DeferredLoad(resultObject, property, key, localCache, configuration, targetType);if (deferredLoad.canLoad()) {// 一級(jí)緩存中已經(jīng)記錄了指定查詢結(jié)果的對(duì)象,直接從緩存中加載對(duì)象,并設(shè)置到外層對(duì)象deferredLoad.load();} else {// 將deferredLoad對(duì)象添加到deferredLoads隊(duì)列中,待整個(gè)外層查詢結(jié)束后再加載結(jié)果對(duì)象deferredLoads.add(new DeferredLoad(resultObject, property, key, localCache, configuration, targetType));} }DeferredLoad是定義在BaseExecutor中的內(nèi)部類,它負(fù)責(zé)從loadCache緩存中延遲加載結(jié)果對(duì)象,含義如下。
/*** 外層對(duì)象對(duì)應(yīng)的MetaObject*/private final MetaObject resultObject;/*** 延遲加載的屬性名稱*/private final String property;/*** 延遲加載的屬性類型*/private final Class<?> targetType;/*** 延遲加載的結(jié)果對(duì)象在一級(jí)緩存中的CacheKey*/private final CacheKey key;/*** 一級(jí)緩存*/private final PerpetualCache localCache;private final ObjectFactory objectFactory;/*** 負(fù)責(zé)結(jié)果對(duì)象的類型轉(zhuǎn)換*/private final ResultExtractor resultExtractor;DeferredLoad的canLoad方法負(fù)責(zé)檢測(cè)緩存項(xiàng)是否已經(jīng)完全加載到緩存中。BaseExecutor的queryFromDatabase方法中,開(kāi)始調(diào)用doQuery查詢數(shù)據(jù)庫(kù)之前,會(huì)先在localCache中放一個(gè)占位符,待查詢完畢后會(huì)將key替換成真實(shí)的數(shù)據(jù),此時(shí)緩存就完全加載了。queryFromDatabase方法的實(shí)現(xiàn)如下。
/*** 從數(shù)據(jù)庫(kù)中查詢** @param ms* @param parameter* @param rowBounds* @param resultHandler* @param key* @param boundSql* @param <E>* @return* @throws SQLException*/ private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {List<E> list;// 先添加一個(gè)占位符,查詢完畢后才將真正的結(jié)果對(duì)象放入緩存,此時(shí)算完全家在localCache.putObject(key, EXECUTION_PLACEHOLDER);try {list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);} finally {// 刪除占位符localCache.removeObject(key);}// 將真正的結(jié)果對(duì)象添加到一級(jí)緩存中l(wèi)ocalCache.putObject(key, list);// 如果是存儲(chǔ)過(guò)程if (ms.getStatementType() == StatementType.CALLABLE) {// 緩存輸出類型的參數(shù)localOutputParameterCache.putObject(key, parameter);}return list; }canLoad和load方法實(shí)現(xiàn)如下。
/*** 判斷是否是完全加載** @return*/public boolean canLoad() {return localCache.getObject(key) != null && localCache.getObject(key) != EXECUTION_PLACEHOLDER;}/*** 負(fù)責(zé)從緩存中加載結(jié)果對(duì)象,設(shè)置到外層對(duì)象 的屬性中*/@SuppressWarnings("unchecked")public void load() {// 從緩存中查詢指定的結(jié)果對(duì)象List<Object> list = (List<Object>) localCache.getObject(key);// 將緩存的結(jié)果對(duì)象轉(zhuǎn)換成指定的類型Object value = resultExtractor.extractObjectFromList(list, targetType);// 設(shè)置到外層對(duì)象的對(duì)應(yīng)屬性resultObject.setValue(property, value);}clearLocalCache方法用于清空緩存。query方法會(huì)根據(jù)flushCache屬性和localCacheScope配置決定是否清空一級(jí)緩存。update方法在執(zhí)行insert、update、delete三類SQL語(yǔ)句之前,會(huì)清空緩存。代碼比較簡(jiǎn)單這里就不貼了。
事務(wù)操作
在BatchExecutor中可以緩存多條SQL,等待合適的時(shí)機(jī)將緩存的多條SQL一起發(fā)送給數(shù)據(jù)庫(kù)執(zhí)行。Executor.flushStatements方法主要是針對(duì)批處理多條SQL語(yǔ)句的,會(huì)調(diào)用doFlushStatements方法處理Executor中緩存的多條SQL語(yǔ)句,在BaseExecutor的commit、rollback方法中會(huì)首先調(diào)用flushStatement方法,再執(zhí)行相關(guān)事務(wù)操作,方法具體的實(shí)現(xiàn)如下。
public List<BatchResult> flushStatements(boolean isRollBack) throws SQLException {if (closed) {throw new ExecutorException("Executor was closed.");}return doFlushStatements(isRollBack); }BaseExecutor.commit方法首先會(huì)清空一級(jí)緩存,調(diào)用flushStatements,最后才根據(jù)參數(shù)決定是否真正提交事務(wù)。代碼如下,
/*** 提交事務(wù)* @param required* @throws SQLException*/ @Override public void commit(boolean required) throws SQLException {if (closed) {throw new ExecutorException("Cannot commit, transaction is already closed");}// 清除緩存clearLocalCache();// 處理緩存的SQLflushStatements();if (required) {// 提交事務(wù)transaction.commit();} }*************************************優(yōu)雅的分割線 **********************************
分享一波:程序員賺外快-必看的巔峰干貨
如果以上內(nèi)容對(duì)你覺(jué)得有用,并想獲取更多的賺錢方式和免費(fèi)的技術(shù)教程
請(qǐng)關(guān)注微信公眾號(hào):HB荷包
一個(gè)能讓你學(xué)習(xí)技術(shù)和賺錢方法的公眾號(hào),持續(xù)更新
*************************************優(yōu)雅的分割線 **********************************
總結(jié)
以上是生活随笔為你收集整理的Mybatis源码阅读(四):核心接口4.2——Executor(上)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 老板思维:有支出必须有对应的收入
- 下一篇: 快学Scala习题解答—第三章 数组相关