1. 程式人生 > >MyBatis 一級快取、二級快取全詳解(一)

MyBatis 一級快取、二級快取全詳解(一)

目錄

  • MyBatis 一級快取、二級快取全詳解(一)
    • 什麼是快取
    • 什麼是MyBatis中的快取
    • MyBatis 中的一級快取
      • 初探一級快取
      • 探究一級快取是如何失效的
    • 一級快取原理探究
    • 還有其他要補充的嗎?
    • 總結

MyBatis 一級快取、二級快取全詳解(一)

什麼是快取

快取就是記憶體中的一個物件,用於對資料庫查詢結果的儲存,用於減少與資料庫的互動次數從而降低資料庫的壓力,進而提高響應速度。

什麼是MyBatis中的快取

MyBatis 中的快取就是說 MyBatis 在執行一次SQL查詢或者SQL更新之後,這條SQL語句並不會消失,而是被MyBatis 快取起來,當再次執行相同SQL語句的時候,就會直接從快取中進行提取,而不是再次執行SQL命令。

MyBatis中的快取分為一級快取和二級快取,一級快取又被稱為 SqlSession 級別的快取,二級快取又被稱為表級快取。

SqlSession是什麼?SqlSession 是SqlSessionFactory會話工廠創建出來的一個會話的物件,這個SqlSession物件用於執行具體的SQL語句並返回給使用者請求的結果。

SqlSession級別的快取是什麼意思?SqlSession級別的快取表示的就是每當執行一條SQL語句後,預設就會把該SQL語句快取起來,也被稱為會話快取

MyBatis 中的一級快取

一級快取是 SqlSession級別 的快取。在操作資料庫時需要構造 sqlSession 物件,在物件中有一個(記憶體區域)資料結構(HashMap)用於儲存快取資料。不同的 sqlSession 之間的快取資料區域(HashMap)是互相不影響的。用一張圖來表示一下一級快取,其中每一個 SqlSession 的內部都會有一個一級快取物件。

在應用執行過程中,我們有可能在一次資料庫會話中,執行多次查詢條件完全相同的SQL,MyBatis 提供了一級快取的方案優化這部分場景,如果是相同的SQL語句,會優先命中一級快取,避免直接對資料庫進行查詢,提高效能。

初探一級快取

我們繼續使用 MyBatis基礎搭建以及配置詳解中的例子(https://mp.weixin.qq.com/s/Ys03zaTSaOakdGU4RlLJ1A)進行 一級快取的探究。

在對應的 resources 根目錄下加上日誌的輸出資訊 log4j.properties

##define an appender named console
log4j.appender.console=org.apache.log4j.ConsoleAppender
#The Target value is System.out or System.err
log4j.appender.console.Target=System.out
#set the layout type of the apperder
log4j.appender.console.layout=org.apache.log4j.PatternLayout
#set the layout format pattern
log4j.appender.console.layout.ConversionPattern=[%-5p] %m%n

##define a logger
log4j.rootLogger=debug,console

模擬思路: 既然每個 SqlSession 都會有自己的一個快取,那麼我們用同一個 SqlSession 是不是就能感受到一級快取的存在呢?呼叫多次 getMapper 方法,生成對應的SQL語句,判斷每次SQL語句是從快取中取還是對資料庫進行操作,下面的例子來證明一下

@Test
public void test(){
  DeptDao deptDao = sqlSession.getMapper(DeptDao.class);
  Dept dept = deptDao.findByDeptNo(1);
  System.out.println(dept);

  DeptDao deptDao2 = sqlSession.getMapper(DeptDao.class);
  Dept dept2 = deptDao2.findByDeptNo(1);
  System.out.println(dept2);
  System.out.println(deptDao2.findByDeptNo(1));
}

輸出:

可以看到,上面程式碼執行了三條相同的SQL語句,但是隻有一條SQL語句進行了輸出,其他兩條SQL語句都是從快取中查詢的,所以它們生成了相同的 Dept 物件。

探究一級快取是如何失效的

上面的一級快取初探讓我們感受到了 MyBatis 中一級快取的存在,那麼現在你或許就會有疑問了,那麼什麼時候快取失效呢? 這個問題也就是我們接下來需要詳細討論的議題之一。

探究更新對一級快取失效的影響

上面的程式碼執行了三次相同的查詢操作,返回了相同的結果,那麼,如果我在第一條和第二條SQL語句之前插入更新的SQL語句,是否會對一級快取產生影響呢?程式碼如下:

@Test
public void testCacheLose(){
  DeptDao deptDao = sqlSession.getMapper(DeptDao.class);
  Dept dept = deptDao.findByDeptNo(1);
  System.out.println(dept);

  // 在兩次查詢之間使用 更新 操作,是否會對一級快取產生影響
  deptDao.insertDept(new Dept(7,"tengxun","shenzhen"));
  //        deptDao.updateDept(new Dept(1,"zhongke","sjz"));
  //        deptDao.deleteByDeptNo(7);

  DeptDao deptDao2 = sqlSession.getMapper(DeptDao.class);
  Dept dept2 = deptDao2.findByDeptNo(1);
  System.out.println(dept2);
}

為了演示效果,就不貼出 insertDept 的程式碼了,就是一條簡單的插入語句。

分別放開不同的更新語句,發現執行效果如下

輸出結果:

如圖所示,在兩次查詢語句中使用插入,會對一級快取進行重新整理,會導致一級快取失效。

探究不同的 SqlSession 對一級快取的影響

如果你看到這裡了,那麼你應該知道一級快取就是 SqlSession 級別的快取,而同一個 SqlSession 會有相同的一級快取,那麼使用不同的 SqlSession 是不是會對一級快取產生影響呢? 顯而易見是的,那麼下面就來演示並且證明一下

private SqlSessionFactory factory; // 把factory設定為全域性變數

@Test
public void testCacheLoseWithSqlSession(){
  DeptDao deptDao = sqlSession.getMapper(DeptDao.class);
  Dept dept = deptDao.findByDeptNo(1);
  System.out.println(dept);

  SqlSession sqlSession2 = factory.openSession();
  DeptDao deptDao2 = sqlSession2.getMapper(DeptDao.class);
  Dept dept2 = deptDao2.findByDeptNo(1);
  System.out.println(dept2);

}

輸出:

上面程式碼使用了不同的 SqlSession 對同一個SQL語句執行了相同的查詢操作,卻對資料庫執行了兩次相同的查詢操作,生成了不同的 dept 物件,由此可見,不同的 SqlSession 是肯定會對一級快取產生影響的。

同一個 SqlSession 使用不同的查詢操作

使用不同的查詢條件是否會對一級快取產生影響呢?可能在你心裡已經有這個答案了,再來看一下程式碼吧

@Test
public void testWithDifferentParam(){
  DeptDao deptDao = sqlSession.getMapper(DeptDao.class);
  Dept dept = deptDao.findByDeptNo(1);
  System.out.println(dept);

  DeptDao deptDao2 = sqlSession.getMapper(DeptDao.class);
  Dept dept2 = deptDao2.findByDeptNo(5);
  System.out.println(dept2);
}

輸出結果

我們在兩次查詢SQL分別使用了不同的查詢條件,查詢出來的資料不一致,那就肯定會對一級快取產生影響了。

手動清理快取對一級快取的影響

我們在兩次查詢的SQL語句之間使用 clearCache 是否會對一級快取產生影響呢?下面例子證實了這一點

@Test
public void testClearCache(){
  DeptDao deptDao = sqlSession.getMapper(DeptDao.class);
  Dept dept = deptDao.findByDeptNo(1);
  System.out.println(dept);

    //在兩次相同的SQL語句之間使用查詢操作,對一級快取的影響。
  sqlSession.clearCache();

  DeptDao deptDao2 = sqlSession.getMapper(DeptDao.class);
  Dept dept2 = deptDao2.findByDeptNo(1);
  System.out.println(dept2);
}

輸出:

我們在兩次查詢操作之間,使用了 sqlSession 的 clearCache() 方法清除了一級快取,所以使用 clearCache 也會對一級快取產生影響。

一級快取原理探究

一級快取到底是什麼?一級快取的工作流程是怎樣的?一級快取何時消失?相信你現在應該會有這幾個疑問,那麼我們本節就來研究一下一級快取的本質

嗯。。。。。。該從何處入手呢?

你可以這樣想,上面我們一直提到一級快取,那麼提到一級快取就繞不開 SqlSession,所以索性我們就直接從 SqlSession ,看看有沒有建立快取或者與快取有關的屬性或者方法

調研了一圈,發現上述所有方法中,好像只有 clearCache() 和快取沾點關係,那麼就直接從這個方法入手吧,分析原始碼時,我們要看它(此類)是誰,它的父類和子類分別又是誰,對如上關係瞭解了,你才會對這個類有更深的認識,分析了一圈,你可能會得到如下這個流程圖

再深入分析,流程走到Perpetualcache 中的 clear() 方法之後,會呼叫其 cache.clear() 方法,那麼這個cache 是什麼東西呢? 點進去發現,cache 其實就是 private Map<Object, Object> cache = new HashMap<Object, Object>(); 也就是一個Map,所以說 cache.clear() 其實就是 map.clear() ,也就是說,快取其實就是本地存放的一個 map 物件,每一個SqlSession 都會存放一個 map 物件的引用,那麼這個 cache 是何時建立的呢?

你覺得最有可能建立快取的地方是哪裡呢? 我覺得是 Executor,為什麼這麼認為? 因為 Executor 是執行器,用來執行SQL請求,而且清除快取的方法也在 Executor 中執行,所以很可能快取的建立也很有可能在 Executor 中,看了一圈發現 Executor 中有一個 createCacheKey 方法,這個方法很像是建立快取的方法啊,跟進去看看,你發現 createCacheKey 方法是由 BaseExecutor 執行的,程式碼如下

CacheKey cacheKey = new CacheKey();
//MappedStatement的id
// id 就是Sql語句的所在位置 包名 + 類名 + SQL名稱
cacheKey.update(ms.getId());
// offset 就是 0
cacheKey.update(rowBounds.getOffset());
// limit 就是 Integer.MAXVALUE
cacheKey.update(rowBounds.getLimit());
// 具體的SQL語句
cacheKey.update(boundSql.getSql());
//後面是update了sql中帶的引數
cacheKey.update(value);
...
if (configuration.getEnvironment() != null) {
  // issue #176
  cacheKey.update(configuration.getEnvironment().getId());
}

建立快取key會經過一系列的 update 方法,update 方法由一個 CacheKey 這個物件來執行的,這個 update 方法最終由 updateList 的 list 來把五個值存進去,對照上面的程式碼和下面的圖示,你應該能理解這五個值都是什麼了

這裡需要注意一下最後一個值, configuration.getEnvironment().getId() 這是什麼,這其實就是定義在 mybatis-config.xml 中的 標籤,見如下。

<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
 <property name="driver" value="${jdbc.driver}"/>
 <property name="url" value="${jdbc.url}"/>
 <property name="username" value="${jdbc.username}"/>
 <property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>

那麼我們迴歸正題,那麼建立完快取之後該用在何處呢?總不會憑空建立一個快取不使用吧?絕對不會的,經過我們對一級快取的探究之後,我們發現一級快取更多是用於查詢操作,畢竟一級快取也叫做查詢快取吧,為什麼叫查詢快取我們一會兒說。我們先來看一下這個快取到底用在哪了,我們跟蹤到 query 方法如下:

@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
  BoundSql boundSql = ms.getBoundSql(parameter);
  // 建立快取
  CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
  return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}

@SuppressWarnings("unchecked")
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
  ...
  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);
  }
  ...
}

// queryFromDatabase 方法
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;
}

如果查不到的話,就從資料庫查,在queryFromDatabase中,會對localcache進行寫入。localcache 物件的put 方法最終交給 Map 進行存放

private Map<Object, Object> cache = new HashMap<Object, Object>();

@Override
public void putObject(Object key, Object value) {
  cache.put(key, value);
}

那麼再說一下為什麼一級快取也叫做查詢快取呢?

我們先來看一下 update 更新方法,先來看一下 update 的原始碼

@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();
  return doUpdate(ms, parameter);
}

BaseExecutor 在每次執行 update 方法的時候,都會先 clearLocalCache() ,所以更新方法並不會有快取,這也就是說為什麼一級快取也叫做查詢快取了,這也就是為什麼我們沒有探究多次執行更新方法對一級快取的影響了。

還有其他要補充的嗎?

我們上面分析了一級快取的執行流程,為什麼一級快取要叫查詢快取以及一級快取組成條件

那麼,你可能看到這感覺這些知識還是不夠連貫,那麼我就幫你把 一級快取的探究小結中的原理說一下吧,為什麼一級快取會失效

  1. 探究更新對一級快取失效的影響: 由上面的分析結論可知,我們每次執行 update 方法時,都會先重新整理一級快取,因為是同一個 SqlSession, 所以是由同一個 Map 進行儲存的,所以此時一級快取會失效
  2. 探究不同的 SqlSession 對一級快取的影響: 這個也就比較好理解了,因為不同的 SqlSession 會有不同的Map 儲存一級快取,然而 SqlSession 之間也不會共享,所以此時也就不存在相同的一級快取
  3. 同一個 SqlSession 使用不同的查詢操作: 這個論點就需要從快取的構成角度來講了,我們通過 cacheKey 可知,一級快取命中的必要條件是兩個 cacheKey 相同,要使得 cacheKey 相同,就需要使 cacheKey 裡面的值相同,也就是

看出差別了嗎?第一個SQL 我們查詢的是部門編號為1的值,而第二個SQL我們查詢的是編號為5的值,兩個快取物件不相同,所以也就不存在快取。

  1. 手動清理快取對一級快取的影響: 由程式設計師自己去呼叫clearCache方法,這個方法就是清除快取的方法,所以也就不存在快取了。

總結

所以此文章到底寫了點什麼呢?拋給你幾個問題了解一下

  1. 什麼是快取?什麼是 MyBatis 快取?
  2. 認識MyBatis快取,MyBatis 一級快取的失效方式
  3. MyBatis 一級快取的執行流程,MyBatis 一級快取究竟是什麼?

文章參考:

mybatis的快取機制(一級快取二級快取和重新整理快取)和mybatis整合ehcache

https://blog.csdn.net/u012373815/article/details/47069223

[聊聊MyBatis快取機制](

公眾號提供 優質Java資料 以及CSDN免費下載 許可權,歡迎你關注我

相關推薦

MyBatis 一級快取二級快取()

目錄 MyBatis 一級快取、二級快取全詳解(一) 什麼是快取 什麼是MyBatis中的快取 MyBatis 中的一級快取 初探一級快取 探究一級快取是如何失效的

Mybatis一級快取二級快取

  Mybatis 一級快取、二級快取 作者 : Stanley 羅昊 【轉載請註明出處和署名,謝謝!】 查詢快取 首先,我們先看一下這個標題“查詢快取”,那就說明跟增、刪、改是沒有任何關聯的,只有在查詢時,才會遇到快取,增刪改不涉及! 查詢快取目前Mybatis

Hibernate的一級快取二級快取和查詢快取

Hibernate的Session提供了一級快取的功能,預設總是有效的,當應用程式儲存持久化實體、修改持久化實體時,Session並不會立即把這種改變提交到資料庫,而是快取在當前的Session中,除非顯示呼叫了Session的flush()方法或通過close()方法關閉Sessi

談Hibernate的一級快取二級快取和查詢快取

Hibernate的Session提供了一級快取的功能,預設總是有效的,當應用程式儲存持久化實體、修改持久化實體時,Session並不會立即把這種改變提交到資料庫,而是快取在當前的Session中,除非顯示呼叫了Session的flush()方法或通過close

物件持久化和一級快取二級快取

1.物件持久化和一級快取 物件的三種狀態(對於Hibernate來說,物件狀態分為三種) (1)暫時態 當物件剛建立和Session沒有發生任何關係時,程式執行完就立刻消失,稱為暫時態 (2)持久態 當執行如下程式碼時,物件變為持久態. Emp e = new

SSM總結:一級快取二級快取分散式快取頁面快取

快取介紹 當處理器讀取資料時,首先會從快取中查詢,如果快取有資料,那麼處理器直接使用,如果快取中中沒有,則從讀取速度相對慢的記憶體中讀取,同時把這個資料塊調入快取中,以便再次使用,這樣可以大大節省讀取記憶體的時間。在JAVA-EE中快取對於資料量大,高併發顯得特別重要。

Elasticsearch增查操作深入()

1、RESTful介面使用方法 為了方便直觀我們使用Head外掛提供的介面進行演示,實際上內部呼叫的RESTful介面。 RESTful介面URL的格式:http://locahost:9200/...... 其中index、type是必須提供的。 id是可選的,不提供es會自

MyBatis一級快取二級快取

一級快取   Mybatis對快取提供支援,但是在沒有配置的預設情況下,它只開啟一級快取,一級快取只是相對於同一個SqlSession而言。所以在引數和SQL完全一樣的情況下,我們使用同一個SqlSession物件呼叫一個Mapper方法,往往只執行一次SQL,因為使用SelSession第一次

mybatis基礎系列(四)——關聯查詢延遲載入一級快取二級快取

關本文是Mybatis基礎系列的第四篇文章,點選下面連結可以檢視前面的文章: mybatis基礎系列(三)——動態sql mybatis基礎系列(二)——基礎語法、別名、輸入對映、輸出對映 mybatis基礎系列(一)——mybatis入門 關聯查詢 在進行表設計時,往往需要在具體的業務基礎上分析表與表之間的

mybatis和hibernate的一級二級快取

MyBatis一級快取: hibernate一級快取: 基本差不多  HashMap本地快取,作用域為session,session級別的快取,通過get,update可以將物件放到一級快取中,當 Session flush 或 close&n

Mybatis一級快取二級快取

注:本筆記是根據尚矽谷的MyBatis視訊記錄的 對於任何一個持久層框架,都有快取機制;快取在電腦中有一塊真實的儲存空間(https://baike.baidu.com/item/%E7%BC%93%E5%AD%98/100710?fr=aladdin); 兩個關於mybatis快取額外的連

Hibernate與Mybatis的區別Hibernate一級快取二級快取之間的區別

Hibernate對資料庫提供了較為完整的封裝,不需要手寫SQL語句,自動生成、自動執行,持久層框架,開源的物件關係對映,對JDBC的進一步封裝。 Mybatis著力點在於JAVA物件與SQL之間的對映關係,需要編寫sql語句,半自動,需要注意的細節更多,但是

MyBatis 延遲載入,一級快取(sqlsession級別)二級快取(mapper級別)設定

什麼是延遲載入          resultMap中的association和collection標籤具有延遲載入的功能。         延遲載入的意思是說,在關聯查詢時,利用延遲載入,先載入主資訊。使用關聯資訊時再去載入關聯資訊。 設定延遲載入      

Mybatis一級二級快取

一級快取 首先做一個測試,建立一個mapper配置檔案和mapper介面,我這裡用了最簡單的查詢來演示。 <mapper namespace="cn.elinzhou.mybatisTest.mapper.UserMapper">

MyBatis 二級快取

目錄 MyBatis 二級快取介紹 二級快取開啟條件 探究二級快取 二級快取失效的條件 第一次SqlSession 未提交 更新對二級快取影

mybatis高階(3)_延遲載入_深度延遲_一級快取_二級快取

mybatis高階(3)_延遲載入_深度延遲_一級快取_二級快取 設定延遲載入需要在mybatis.xml中設定 注: 侵入式延遲載入為真時是延遲載入 侵入式延遲載入為假時是深度延遲載入 <!-- 延遲載入和深度延遲載入 --> <settings

Mybatis二級快取

  MyBatis快取介紹   Mybatis和Hibernate一樣,也有一級和二級快取,同樣預設開啟的只有一級快取,二級快取也需要手動配置開啟。        Mybatis 提供了快取機制減輕資料庫壓力,提高資料庫效能 一

mybatis 一級快取二級快取 配置使用

mybatis提供查詢快取,用於減輕資料壓力,提高資料庫效能。 mybaits提供一級快取,和二級快取。 1、一級快取 ​ MyBatis 預設開啟了一級快取,一級快取是在SqlSession 層面進行快取的。即,同一個SqlSession ,多次呼叫同一個Mapp

mybatis 第六篇 MyBatis一級快取二級快取

一、一級快取 1.什麼是一級快取 每當我們使用mybatis開啟一次資料庫會話,mybaits就會建立一個sqlSession物件。 在一次資料庫會話中,當我們執行完全相同的sql語句,為了解決資源浪費

Mybatis一級快取二級快取

Mybatis的快取 Mybatis的快取,包括一級快取和二級快取 一級快取是預設使用的。 二級快取需要手動開啟。 一級快取 一級快取指的就是sqlsession,在sqlsession中有一個數據區域,是map結構,這個區域就是一級快取區域。一級快取中的key