首頁 Java java教程 【MyBatis源碼解析】MyBatis一二級緩存

【MyBatis源碼解析】MyBatis一二級緩存

Jun 26, 2017 am 10:00 AM
mybatis 原始碼 解析

MyBatis快取

我們知道,頻繁的資料庫操作是非常耗費效能的(主要是因為對於DB而言,資料是持久化在磁碟中的,因此查詢操作需要通過IO,IO操作速度相比內存操作速度慢了好幾個量級),尤其是對於一些相同的查詢語句,完全可以把查詢結果存儲起來,下次查詢同樣的內容的時候直接從記憶體中取得資料即可,這樣在某些場景下可以大大提升查詢效率。

MyBatis的快取分為兩種:

  1. 一級緩存,一級快取是SqlSession等級的緩存,對於相同的查詢,會從快取中傳回結果而不是查詢資料庫

  2. 二級緩存,二級緩存是Mapper層級的緩存,定義在Mapper檔案的標籤中並需要開啟此緩存,多個Mapper檔案可以共用一個緩存,依賴< cache-ref>標籤配置

下面來詳細看一下MyBatis的一二級快取。

 

MyBatis一級快取工作流程

接著看MyBatis一級快取工作流程。前面說了,MyBatis的一級緩存是SqlSession級別的緩存,當openSession()的方法運行完畢或主動調用了SqlSession的close方法,SqlSession就被回收了,一級緩存與此同時也一起被回收掉了。前面的文章有說過,在MyBatis中,無論selectOne還是selectList方法,最後都被轉換為了selectList方法來執行,那麼看一下SqlSession的selectList方法的實作:

#
 1 public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) { 2     try { 3       MappedStatement ms = configuration.getMappedStatement(statement); 4       return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER); 5     } catch (Exception e) { 6       throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e); 7     } finally { 8       ErrorContext.instance().reset(); 9     }10 }
登入後複製
登入後複製

繼續追蹤第4行的程式碼,到BaseExeccutor的query方法:

1 public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {2     BoundSql boundSql = ms.getBoundSql(parameter);3     CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);4     return query(ms, parameter, rowBounds, resultHandler, key, boundSql);5 }
登入後複製

第3行建置快取條件CacheKey,這裡牽涉到怎麼樣條件算是和上一次查詢是同一個條件的問題,因為同一個條件就可以回傳上一次的結果回去,這部分程式碼留在下一部分分析。

接著看第4行的query方法的實現,程式碼位於CachingExecutor中:

 1 public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) 2       throws SQLException { 3     Cache cache = ms.getCache(); 4     if (cache != null) { 5       flushCacheIfRequired(ms); 6       if (ms.isUseCache() && resultHandler == null) { 7         ensureNoOutParams(ms, parameterObject, boundSql); 8         @SuppressWarnings("unchecked") 9         List<E> list = (List<E>) tcm.getObject(cache, key);10         if (list == null) {11           list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);12           tcm.putObject(cache, key, list); // issue #578 and #11613         }14         return list;15       }16     }17     return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);18 }
登入後複製
登入後複製

第3行~第16行的程式碼先不管,繼續跟第17行的query方法,程式碼位於BaseExecutor:

 1 public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { 2     ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId()); 3     if (closed) { 4       throw new ExecutorException("Executor was closed."); 5     } 6     if (queryStack == 0 && ms.isFlushCacheRequired()) { 7       clearLocalCache(); 8     } 9     List<E> list;10     try {11       queryStack++;12       list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;13       if (list != null) {14         handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);15       } else {16         list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);17       }18     } finally {19       queryStack--;20     }21     ...22 }
登入後複製

看12行,query的时候会尝试从localCache中去获取查询结果,如果获取到的查询结果为null,那么执行16行的代码从DB中捞数据,捞完之后会把CacheKey作为key,把查询结果作为value放到localCache中。

MyBatis一级缓存存储流程看完了,接着我们从这段代码中可以得到三个结论:

  1. MyBatis的一级缓存是SqlSession级别的,但是它并不定义在SqlSessio接口的实现类DefaultSqlSession中,而是定义在DefaultSqlSession的成员变量Executor中,Executor是在openSession的时候被实例化出来的,它的默认实现为SimpleExecutor

  2. MyBatis中的一级缓存,与有没有配置无关,只要SqlSession存在,MyBastis一级缓存就存在,localCache的类型是PerpetualCache,它其实很简单,一个id属性+一个HashMap属性而已,id是一个名为"localCache"的字符串,HashMap用于存储数据,Key为CacheKey,Value为查询结果

  3. MyBatis的一级缓存查询的时候默认都是会先尝试从一级缓存中获取数据的,但是我们看第6行的代码做了一个判断,ms.isFlushCacheRequired(),即想每次查询都走DB也行,将标签所在的Mapper的Namespace+标签中定义的sql语句

即只要两次查询满足以上三个条件且没有定义flushCache="true",那么第二次查询会直接从MyBatis一级缓存PerpetualCache中返回数据,而不会走DB。

 

MyBatis二级缓存

上面说完了MyBatis,接着看一下MyBatis二级缓存,还是从二级缓存工作流程开始。还是从DefaultSqlSession的selectList方法进去:

 1 public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) { 2     try { 3       MappedStatement ms = configuration.getMappedStatement(statement); 4       return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER); 5     } catch (Exception e) { 6       throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e); 7     } finally { 8       ErrorContext.instance().reset(); 9     }10 }
登入後複製
登入後複製

执行query方法,方法位于CachingExecutor中:

1 public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {2     BoundSql boundSql = ms.getBoundSql(parameterObject);3     CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);4     return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);5 }
登入後複製

继续跟第4行的query方法,同样位于CachingExecutor中:

 1 public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) 2       throws SQLException { 3     Cache cache = ms.getCache(); 4     if (cache != null) { 5       flushCacheIfRequired(ms); 6       if (ms.isUseCache() && resultHandler == null) { 7         ensureNoOutParams(ms, parameterObject, boundSql); 8         @SuppressWarnings("unchecked") 9         List<E> list = (List<E>) tcm.getObject(cache, key);10         if (list == null) {11           list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);12           tcm.putObject(cache, key, list); // issue #578 and #11613         }14         return list;15       }16     }17     return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);18 }
登入後複製
登入後複製

从这里看到,执行第17行的BaseExecutor的query方法之前,会先拿Mybatis二级缓存,而BaseExecutor的query方法会优先读取MyBatis一级缓存,由此可以得出一个重要结论:假如定义了MyBatis二级缓存,那么MyBatis二级缓存读取优先级高于MyBatis一级缓存

而第3行~第16行的逻辑:

  • 第5行的方法很好理解,根据flushCache=true或者flushCache=false判断是否要清理二级缓存

  • 第7行的方法是保证MyBatis二级缓存不会存储存储过程的结果

  • 第9行的方法先尝试从tcm中获取查询结果,这个tcm解释一下,这又是一个装饰器模式(数数MyBatis用到了多少装饰器模式了),创建一个事物缓存TranactionalCache,持有Cache接口,Cache接口的实现类就是根据我们在Mapper文件中配置的创建的Cache实例

  • 第10行~第12行,如果没有从MyBatis二级缓存中拿到数据,那么就会查一次数据库,然后放到MyBatis二级缓存中去

至于如何判定上次查询和这次查询是一次查询?由于这里的CacheKey和MyBatis一级缓存使用的是同一个CacheKey,因此它的判定条件和前文写过的MyBatis一级缓存三个维度的判定条件是一致的。

最后再来谈一点,"Cache cache = ms.getCache()"这句代码十分重要,这意味着Cache是从MappedStatement中获取到的,而MappedStatement又和每一个標籤所在的Mapper的Namespace+標籤中定義的sql語句

#對MapperA來說,其中的任何條件都沒有變化,自然會將原結果回傳。

這個問題對於MyBatis的二級快取來說是一個無解的問題,因此使用MyBatis二級快取有一個前提:必須保證所有的增刪改查都在同一個命名空間下才行

以上是【MyBatis源碼解析】MyBatis一二級緩存的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

熱門話題

Java教學
1655
14
CakePHP 教程
1414
52
Laravel 教程
1307
25
PHP教程
1253
29
C# 教程
1228
24
詳解MyBatis動態SQL標籤中的Set標籤功能 詳解MyBatis動態SQL標籤中的Set標籤功能 Feb 26, 2024 pm 07:48 PM

MyBatis動態SQL標籤解讀:Set標籤用法詳解MyBatis是一個優秀的持久層框架,它提供了豐富的動態SQL標籤,可以靈活地建構資料庫操作語句。其中,Set標籤是用來產生UPDATE語句中SET子句的標籤,在更新作業中非常常用。本文將詳細解讀MyBatis中Set標籤的用法,以及透過具體的程式碼範例來示範其功能。什麼是Set標籤Set標籤用於MyBati

Oracle錯誤3114詳解:如何快速解決 Oracle錯誤3114詳解:如何快速解決 Mar 08, 2024 pm 02:42 PM

Oracle錯誤3114詳解:如何快速解決,需要具體程式碼範例在Oracle資料庫開發與管理過程中,我們常常會遇到各種各樣的錯誤,其中錯誤3114是比較常見的一個問題。錯誤3114通常表示資料庫連線出現問題,可能是網路故障、資料庫服務停止、或連接字串設定不正確等原因導致的。本文將詳細解釋錯誤3114的產生原因,以及如何快速解決這個問題,並附上具體的程式碼

解析Wormhole NTT:適用於任何Token的開放框架 解析Wormhole NTT:適用於任何Token的開放框架 Mar 05, 2024 pm 12:46 PM

Wormhole在區塊鏈互通性方面處於領先地位,專注於創建有彈性、面向未來的去中心化系統,優先考慮所有權、控制權和無需許可的創新。這個願景的基礎是對技術專業知識、道德原則和社群一致性的承諾,旨在以簡單、清晰和廣泛的多鏈解決方案套件重新定義互通性格局。隨著零知識證明、擴容方案和功能豐富的Token標準的興起,區塊鏈變得更加強大,而互通性也變得越來越重要。在這個不斷創新的應用程式環境中,新穎的治理系統和實用功能為整個網路的資產帶來了前所未有的機會。協議建構者現在正在努力思考如何在這個新興的多鏈

解析MyBatis的快取機制:比較一級快取和二級快取的特性和用法 解析MyBatis的快取機制:比較一級快取和二級快取的特性和用法 Feb 25, 2024 pm 12:30 PM

MyBatis的快取機制解析:一級快取與二級快取的差異與應用在MyBatis框架中,快取是一個非常重要的特性,可以有效提升資料庫操作的效能。其中,一級快取和二級快取是MyBatis常用的兩種快取機制。本文將詳細解析一級快取與二級快取的差異與應用,並提供具體的程式碼範例進行說明。一、一級緩存一級緩存也被稱為本地緩存,它預設開啟且不可關閉。一級快取是SqlSes

PHP 中點的意思和用法解析 PHP 中點的意思和用法解析 Mar 27, 2024 pm 08:57 PM

【PHP中點的意義和用法解析】在PHP中,中點(.)是常用的運算符,用來連接兩個字串或物件的屬性或方法。在本文中,我們將深入探討PHP中點的意義和用法,並透過具體的程式碼範例加以說明。 1.連接字串中點運算子.在PHP中最常見的用法是連接兩個字串。透過將.放置在兩個字串之間,可以將它們拼接在一起,形成一個新的字串。 $string1=&qu

PHP程式碼在瀏覽器中如何顯示原始碼而不被解釋執行? PHP程式碼在瀏覽器中如何顯示原始碼而不被解釋執行? Mar 11, 2024 am 10:54 AM

PHP程式碼在瀏覽器中如何顯示原始碼而不被解釋執行? PHP是一種伺服器端腳本語言,通常用於開發動態網頁。當PHP檔案在伺服器上被要求時,伺服器會解釋執行其中的PHP程式碼,並將最終的HTML內容傳送到瀏覽器以供顯示。然而,有時我們希望在瀏覽器中直接展示PHP檔案的原始碼,而不是被執行。本文將介紹如何在瀏覽器中顯示PHP程式碼的源碼,而不被解釋執行。在PHP中,可以使

Apache2無法正確解析PHP檔案的處理方法 Apache2無法正確解析PHP檔案的處理方法 Mar 08, 2024 am 11:09 AM

由於篇幅限制,以下是一個簡短的文章:Apache2是常用的Web伺服器軟體,而PHP是廣泛使用的伺服器端腳本語言。在建置網站過程中,有時會遇到Apache2無法正確解析PHP檔案的問題,導致PHP程式碼無法執行。這種問題通常是因為Apache2沒有正確配置PHP模組,或是PHP模組與Apache2的版本不相容所導致的。解決這個問題的方法一般有兩種,一種是

Win11新功能解析:跳過登入微軟帳號的方法 Win11新功能解析:跳過登入微軟帳號的方法 Mar 27, 2024 pm 05:24 PM

Win11新功能解析:跳過登入微軟帳號的方法隨著Windows11的發布,許多用戶發現其帶來了更多的便利性和新功能。然而,有些用戶可能不喜歡將其係統與微軟帳戶綁定,希望跳過這一步驟。本文將介紹一些方法,幫助使用者在Windows11中跳過登入微軟帳戶,並實現更私密、更自主的使用體驗。首先,讓我們來了解為什麼有些用戶不願意登入微軟帳號。一方面,一些用戶擔心他們

See all articles