日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Mybatis源码阅读(四):核心接口4.2——Executor(上)

發布時間:2025/3/11 编程问答 42 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Mybatis源码阅读(四):核心接口4.2——Executor(上) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

*************************************優雅的分割線 **********************************

分享一波:程序員賺外快-必看的巔峰干貨

如果以上內容對你覺得有用,并想獲取更多的賺錢方式和免費的技術教程

請關注微信公眾號: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荷包

一個能讓你學習技術和賺錢方法的公眾號,持續更新
*************************************優雅的分割線 **********************************

創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎

總結

以上是生活随笔為你收集整理的Mybatis源码阅读(四):核心接口4.2——Executor(上)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。