通過簡單的人話來理解MyBatis的緩存

MyBatis緩存概述

緩存是啥?

1.通俗的講:緩存就是臨時的數據;
  比如:我們下載一款遊戲,開始玩的時候,可能比較慢,這個時候這塊遊戲應用實際上在下載一些實際你選擇的遊戲場景的資源;
       一旦下載完成,你玩了第一局之後,再次玩第二局就能夠很快的進入(暫且不考慮中途遊戲應用有突然更新)遊戲中,
       這就是因爲你的遊戲應用已經緩存了當前遊戲場景的資源;

MyBatis緩存體系結構

代碼結構

在這裏插入圖片描述

一級緩存

一級緩存概述

1.一級緩存也稱爲本地緩存
2.一級緩存主要是針對的同一個SqlSession對象的緩存:PerpetualCache localCache;
  如果同一個SqlSession執行的SQL一模一樣,會直接使用緩存中的查詢結果

一級緩存的開啓:默認開啓

一級緩存的存放位置

在這裏插入圖片描述

一級緩存的關閉:無法關閉

一級緩存的案例說明

相同SqlSession查詢

代碼測試

  @Test
  public void firstLevelDemoSameSqlSession() throws IOException {
    String resource = "mybatis-config-session-cache-first.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

    SqlSession session = sqlSessionFactory.openSession(); // ExecutorType.BATCH

    try {
      BlogMapper mapper = session.getMapper(BlogMapper.class);
      Blog blog = mapper.selectByPrimaryKey(1L);
      System.out.println("blog ="+blog.toString());
      Blog blog1 = mapper.selectByPrimaryKey(1L);
      System.out.println("blog1="+blog1);

      BlogMapper mapper1 = session.getMapper(BlogMapper.class);
      Blog blog2 = mapper1.selectByPrimaryKey(1L);
      System.out.println("blog2="+blog2);

    } finally {
      session.close();
    }
  }

在這裏插入圖片描述

控臺輸出

在這裏插入圖片描述

不同SqlSession查詢

代碼測試

@Test
  public void firstLevelDemoDifferentSqlSession() throws IOException {
    String resource = "mybatis-config-session-cache-first.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

    SqlSession session = sqlSessionFactory.openSession(); // ExecutorType.BATCH
    SqlSession session1 = sqlSessionFactory.openSession(); // ExecutorType.BATCH

    try {
      BlogMapper mapper = session.getMapper(BlogMapper.class);
      Blog blog = mapper.selectByPrimaryKey(1L);
      System.out.println(blog.toString());

      BlogMapper mapper1 = session1.getMapper(BlogMapper.class);
      Blog blog1 = mapper1.selectByPrimaryKey(1L);
      System.out.println(blog1);
    } finally {
      session.close();
    }
  }

在這裏插入圖片描述

控臺輸出

在這裏插入圖片描述

相同SqlSession,更新操作會清理緩存

代碼測試

@Test
  public void firstLevelDemoSameSqlSessionQueryAndUpdate() throws IOException {
    String resource = "mybatis-config-session-cache-first.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

    SqlSession session = sqlSessionFactory.openSession(); // ExecutorType.BATCH

    try {
      BlogMapper mapper = session.getMapper(BlogMapper.class);
      Blog blog = mapper.selectByPrimaryKey(1L);
      System.out.println("blog ="+blog.toString());

      Blog updateRecord=new Blog();
      updateRecord.setBid(blog.getBid());
      updateRecord.setName("U"+blog.getName());
      mapper.updateByPrimaryKey(updateRecord);
      session.commit();

      Blog blog1 = mapper.selectByPrimaryKey(1L);
      System.out.println("blog1="+blog1);


    } finally {
      session.close();
    }
  }
一級緩存是在 BaseExecutor 中的 update()方法中調用 clearLocalCache()清空的 (無條件),query 中會判斷。

控臺輸出

在這裏插入圖片描述

1.同一個session中途一旦有更新操作(update/delete),那麼一級緩存中的該Session中緩存會清理掉

不同SqlSession更新,相同SqlSession查詢

代碼測試

@Test
  public void firstLevelDemoDifferentSqlSessionUpdateAndQuery() throws IOException {
    String resource = "mybatis-config-session-cache-first.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

    SqlSession session = sqlSessionFactory.openSession(); // ExecutorType.BATCH

    try {
      BlogMapper mapper  = session.getMapper(BlogMapper.class);
      Blog blog = mapper.selectByPrimaryKey(1L);
      System.out.println("blog="+blog);

      SqlSession session1 = sqlSessionFactory.openSession(); // ExecutorType.BATCH
      BlogMapper mapper1 = session1.getMapper(BlogMapper.class);
      Blog updateRecord=new Blog();
      updateRecord.setBid(1L);
      updateRecord.setName("frank");
      mapper1.updateByPrimaryKey(updateRecord);
      session1.commit();

      Blog blog1 = mapper.selectByPrimaryKey(1L);
      System.out.println("blog1="+blog1);


    } finally {
      session.close();
    }
  }

控臺輸出

在這裏插入圖片描述

1.在這個案例中,session進行了第一次查詢,返回的name 是gaoxinfu
  緊接着,session1進行更新操作,name變爲frank
  然後,session進行再次查詢,由於命中了緩存,所以直接拿了緩存中的內容,name爲gaoxinfu
2.這樣的話,使用一級緩存的時候,因爲緩存不能跨會話共享,不同的會話之間對於相同的數據 可能有不一樣的緩存。
  在有多個會話或者分佈式環境下,會存在髒數據的問題。如果要 解決這個問題,就要用到二級緩存。  

案例源碼地址

https://gitee.com/gaoxinfu_admin/open-source/blob/master/mybatis/mybatis-3-master/demo-open-source-mybatis/src/main/java/com/gaoxinfu/demo/open/source/mybatis/cache/first/FirstLevelDemo.java

一級緩存源碼使用位置

一級緩存源碼使用位置-查詢

BaseExecutor.query方法

 @SuppressWarnings("unchecked")
  @Override
  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.");
    }
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }
    List<E> list;
    try {
      queryStack++;
      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) {
      for (DeferredLoad deferredLoad : deferredLoads) {
        deferredLoad.load();
      }
      // issue #601
      deferredLoads.clear();
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482
        clearLocalCache();
      }
    }
    return list;
  }

在這裏插入圖片描述

一級緩存源碼使用位置-更新

在這裏插入圖片描述

  @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);
  }
  @Override
  public void clearLocalCache() {
    if (!closed) {
      localCache.clear();
      localOutputParameterCache.clear();
    }
  }
同一個session中途一旦有更新操作(update/delete),那麼一級緩存中的該Session中緩存會清理掉

附:關於上面的key,如何定義

在這裏插入圖片描述

二級緩存

二級緩存概述

1.從上面的”不同SqlSession更新,Session相同查詢“中我們知道,一級緩存存在一個問題:一級緩存不能跨會話共享數據;
2.二級緩存主要是解決上面一級緩存中的問題(一級緩存不能共享數據),
3.二級緩存的有效範圍是namespace級別的,可以被多個SqlSession共享(只要是同一個接口裏面的相同方法,都可以共享);

二級緩存範圍與順序

1.二級緩存,相同的Namespace,由於不同的SqlSession的數據可以進行數據的共享
  因此,二級緩存的查詢範圍是要大於一級緩存的;  

二級緩存與一級緩存的查詢順序:先查詢二級緩存,再次查詢一級緩存

作爲一個作用範圍更廣的緩存,它肯定是在 SqlSession 的外層,否則不可能被多個 SqlSession 共享。
而一級緩存是在 SqlSession 內部的,肯定是工作在一級緩存之前,也就是隻有取不到二級緩存的情況下才到一個會話中去取一級緩存。

二級緩存存放位置:CachingExecutor.TransactionalCacheManager

1.對於二級緩存的處理,有一個裝飾器類CachingExecutor 進行特殊的處理,
  如果配置中已經開啓了二級緩存,那麼創建 Executor 對象的時候會對通過CachingExecutor對Executor進行裝飾。
2.CachingExecutor 對於查詢請求,會判斷二級緩存是否有緩存結果,如果有就直接返回,
  如果沒有委派交給真正的查詢器Executor實現類,比如 SimpleExecutor來執行查詢,再走到一級緩存的流程。
  最後會把結果緩存起來,並且返回給用戶。

在這裏插入圖片描述

二級緩存開啓方式

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章