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