天天看點

MyBatis(七):MyBatis緩存詳解(一級緩存/二級緩存)

  1. 一級緩存

    ​ MyBatis一級緩存上SqlSession緩存,即在統一SqlSession中,在不執行增删改操作送出事務的前提下,對同一條資料進行多次查詢時,第一次查詢從資料庫中查詢,完成後會存入緩存,其餘從緩存中直接讀取。MyBatis一級緩存預設開啟。

    MyBatis(七):MyBatis緩存詳解(一級緩存/二級緩存)
  2. 二級緩存

    ​ MyBatis二級緩存是命名空間NameSpace緩存,也可了解為二級緩存被多個SqlSession共享,是一個全局變量。

    ​ 二級緩存預設是關閉的,需要手動配置進行開啟。開啟二級緩存後,資料查詢流程為:二級緩存->一級緩存->資料庫。

    • 二級緩存開啟
      1. 實體類需要實作Serializable接口
      2. 核心配置檔案增加标簽
      <settings> 
        <setting name="cacheEnabled" value="true"/> 
      </settings>
                 
      1. 在需要開啟的mapper中添加标簽
      <!--開啟二級緩存--> 
      <cache/>
                 
      • eviction

        屬性可以設定緩存回收政策,預設LRU政策
        • LRU

          - 最近最少回收,移除最長時間不被使用的對象
        • FIFO

          - 先進先出,按照緩存進入的順序來移除它們
        • SOFT

          - 軟引用,移除基于垃圾回收器狀态和軟引用規則的對象
        • WEAK

          - 弱引用,更積極的移除基于垃圾收集器和弱引用規則的對象
      • flushinterval

        緩存重新整理間隔,緩存多長時間重新整理一次,預設不清空,設定一個毫秒值
      • readOnly

        : 是否隻讀;

        false讀寫(預設):MyBatis 覺得資料可能會被修改

        true 隻讀,MyBatis 認為所有從緩存中擷取資料的操作都是隻讀操作,不會修改資料。MyBatis 為了加快擷取資料,直接就會将資料在緩存中的引用交給使用者。不安全,速度快。

      • size

        : 緩存存放多少個元素
      • type

        : 指定自定義緩存的全類名(實作Cache 接口即可)
      • blocking

        : 若緩存中找不到對應的key,是否會一直blocking,直到有對應的資料進入緩存。

        例如:

        <cache eviction="LRU" flushInterval="1000*60*60*24*7" readOnly="true" blocking="false" type="MyCache" size="1000"/>
                   
    • 二級緩存測試,多個SqlSession
      InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
      SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
      
      User param = new User();
      param.setId(1);
      
      SqlSession sqlSession1 = sqlSessionFactory.openSession();
      IUserDao userDao1 = sqlSession1.getMapper(IUserDao.class);
      User user1 = userDao1.findOne(param);
      System.out.println(user1);
      sqlSession1.close();
      
      SqlSession sqlSession2 = sqlSessionFactory.openSession();
      IUserDao userDao2 = sqlSession2.getMapper(IUserDao.class);
      User user2 = userDao2.findOne(param);
      System.out.println(user2);
      sqlSession2.close();
                 
      控制台檢視輸出日志,發現緩存已命中—Cache Hit Ratio [com.rangers.dao.IUserDao]: 0.5
      14:26:48,608 DEBUG IUserDao:62 - Cache Hit Ratio [com.rangers.dao.IUserDao]: 0.0
      14:26:48,611 DEBUG JdbcTransaction:137 - Opening JDBC Connection
      14:26:48,952 DEBUG PooledDataSource:406 - Created connection 543846639.
      14:26:48,952 DEBUG JdbcTransaction:101 - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@206a70ef]
      14:26:48,957 DEBUG findOne:159 - ==>  Preparing: select * from user where id=? 
      14:26:48,980 DEBUG findOne:159 - ==> Parameters: 1(Integer)
      14:26:49,004 DEBUG findOne:159 - <==      Total: 1
      com.rangers.entity.User{id=1, name='rangers', address='杭州'}
      14:26:49,007 DEBUG JdbcTransaction:123 - Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@206a70ef]
      14:26:49,010 DEBUG JdbcTransaction:91 - Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@206a70ef]
      14:26:49,011 DEBUG PooledDataSource:363 - Returned connection 543846639 to pool.
      14:26:49,014 DEBUG IUserDao:62 - Cache Hit Ratio [com.rangers.dao.IUserDao]: 0.5
      com.rangers.entity.User{id=1, name='rangers', address='杭州'}
                 
    • 二級緩存失效條件
      • 首次SqlSession未送出事務時,二級緩存無法命中

        SqlSession未送出時,執行結果未放入二級緩存中,這時候第二個SqlSession在查詢時候是無法命中的

        例如:調整sqlSession1.close();在sqlSession2執行之後,就不會命中緩存

        InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        
        User param = new User();
        param.setId(1);
        
        SqlSession sqlSession1 = sqlSessionFactory.openSession();
        IUserDao userDao1 = sqlSession1.getMapper(IUserDao.class);
        User user1 = userDao1.findOne(param);
        System.out.println(user1);
        
        SqlSession sqlSession2 = sqlSessionFactory.openSession();
        IUserDao userDao2 = sqlSession2.getMapper(IUserDao.class);
        User user2 = userDao2.findOne(param);
        System.out.println(user2);
        
        sqlSession1.close();
        sqlSession2.close();
                   
        檢視控制台輸出,發現兩次緩存命中都是0.0,Cache Hit Ratio [com.rangers.dao.IUserDao]: 0.0
        14:49:39,073 DEBUG IUserDao:62 - Cache Hit Ratio [com.rangers.dao.IUserDao]: 0.0
        14:49:39,075 DEBUG JdbcTransaction:137 - Opening JDBC Connection
        14:49:39,385 DEBUG PooledDataSource:406 - Created connection 543846639.
        14:49:39,385 DEBUG JdbcTransaction:101 - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@206a70ef]
        14:49:39,389 DEBUG findOne:159 - ==>  Preparing: select * from user where id=? 
        14:49:39,408 DEBUG findOne:159 - ==> Parameters: 1(Integer)
        14:49:39,433 DEBUG findOne:159 - <==      Total: 1
        com.rangers.entity.User{id=1, name='rangers', address='杭州'}
        14:49:39,434 DEBUG IUserDao:62 - Cache Hit Ratio [com.rangers.dao.IUserDao]: 0.0
        14:49:39,434 DEBUG JdbcTransaction:137 - Opening JDBC Connection
        14:49:39,489 DEBUG PooledDataSource:406 - Created connection 2079179914.
        14:49:39,489 DEBUG JdbcTransaction:101 - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@7bedc48a]
        14:49:39,491 DEBUG findOne:159 - ==>  Preparing: select * from user where id=? 
        14:49:39,492 DEBUG findOne:159 - ==> Parameters: 1(Integer)
        14:49:39,495 DEBUG findOne:159 - <==      Total: 1
        com.rangers.entity.User{id=1, name='rangers', address='杭州'}
        14:49:39,497 DEBUG JdbcTransaction:123 - Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@206a70ef]
        14:49:39,499 DEBUG JdbcTransaction:91 - Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@206a70ef]
        14:49:39,500 DEBUG PooledDataSource:363 - Returned connection 543846639 to pool.
        14:49:39,500 DEBUG JdbcTransaction:123 - Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@7bedc48a]
        14:49:39,502 DEBUG JdbcTransaction:91 - Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@7bedc48a]
        14:49:39,502 DEBUG PooledDataSource:363 - Returned connection 2079179914 to pool.
                   
      • 更新送出事務,與一級緩存一樣,增删改操作送出事務會清空緩存
        InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        
        User param = new User();
        param.setId(1);
        
        SqlSession sqlSession1 = sqlSessionFactory.openSession();
        IUserDao userDao1 = sqlSession1.getMapper(IUserDao.class);
        User user1 = userDao1.findOne(param);
        System.out.println("sqlSession1查詢結果:"+user1);
        sqlSession1.close();
        
        SqlSession sqlSession2 = sqlSessionFactory.openSession();
        IUserDao userDao2 = sqlSession2.getMapper(IUserDao.class);
        param.setName("遊騎兵");
        param.setAddress("西安");
        Boolean flag = userDao2.updateUser(param) > 0;
        System.out.println("執行更新操作結果:"+flag);
        sqlSession2.commit();
        sqlSession2.close();
        
        SqlSession sqlSession3 = sqlSessionFactory.openSession();
        IUserDao userDao3 = sqlSession3.getMapper(IUserDao.class);
        User user3 = userDao3.findOne(param);
        sqlSession3.close();
        System.out.println("sqlSession3查詢結果:"+user3);
                   
        檢視控制台輸出,經過sqlSession2送出事務後,sqlSession3的查詢緩存命中率為0.0
        15:00:33,901 DEBUG IUserDao:62 - Cache Hit Ratio [com.rangers.dao.IUserDao]: 0.0
        15:00:33,904 DEBUG JdbcTransaction:137 - Opening JDBC Connection
        15:00:34,263 DEBUG PooledDataSource:406 - Created connection 543846639.
        15:00:34,263 DEBUG JdbcTransaction:101 - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@206a70ef]
        15:00:34,267 DEBUG findOne:159 - ==>  Preparing: select * from user where id=? 
        15:00:34,290 DEBUG findOne:159 - ==> Parameters: 1(Integer)
        15:00:34,314 DEBUG findOne:159 - <==      Total: 1
        sqlSession1查詢結果:com.rangers.entity.User{id=1, name='rangers', address='杭州'}
        15:00:34,317 DEBUG JdbcTransaction:123 - Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@206a70ef]
        15:00:34,321 DEBUG JdbcTransaction:91 - Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@206a70ef]
        15:00:34,321 DEBUG PooledDataSource:363 - Returned connection 543846639 to pool.
        15:00:34,321 DEBUG JdbcTransaction:137 - Opening JDBC Connection
        15:00:34,322 DEBUG PooledDataSource:398 - Checked out connection 543846639 from pool.
        15:00:34,322 DEBUG JdbcTransaction:101 - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@206a70ef]
        15:00:34,325 DEBUG updateUser:159 - ==>  Preparing: update user set name=?,address=? where id=? 
        15:00:34,325 DEBUG updateUser:159 - ==> Parameters: 遊騎兵(String), 西安(String), 1(Integer)
        15:00:34,334 DEBUG updateUser:159 - <==    Updates: 1
        執行更新操作結果:true
        15:00:34,335 DEBUG JdbcTransaction:70 - Committing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@206a70ef]
        15:00:34,445 DEBUG JdbcTransaction:123 - Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@206a70ef]
        15:00:34,448 DEBUG JdbcTransaction:91 - Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@206a70ef]
        15:00:34,450 DEBUG PooledDataSource:363 - Returned connection 543846639 to pool.
        15:00:34,450 DEBUG IUserDao:62 - Cache Hit Ratio [com.rangers.dao.IUserDao]: 0.0
        15:00:34,450 DEBUG JdbcTransaction:137 - Opening JDBC Connection
        15:00:34,451 DEBUG PooledDataSource:398 - Checked out connection 543846639 from pool.
        15:00:34,451 DEBUG JdbcTransaction:101 - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@206a70ef]
        15:00:34,453 DEBUG findOne:159 - ==>  Preparing: select * from user where id=? 
        15:00:34,454 DEBUG findOne:159 - ==> Parameters: 1(Integer)
        15:00:34,458 DEBUG findOne:159 - <==      Total: 1
        15:00:34,458 DEBUG JdbcTransaction:123 - Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@206a70ef]
        15:00:34,461 DEBUG JdbcTransaction:91 - Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@206a70ef]
        15:00:34,462 DEBUG PooledDataSource:363 - Returned connection 543846639 to pool.
        sqlSession3查詢結果:com.rangers.entity.User{id=1, name='遊騎兵', address='西安'}
                   
    • 多表操作時,對MyBatis二級緩存的影響
      1. 問題:未同一namespace下進行增删改送出操作時,其他namespace的緩存是無法感覺的

      例如:兩個Mapper檔案,AMapper.xml和BMapper.xml,B修改了user表中的内容,A是感覺不到的,那麼再從A裡查詢如果用到了緩存,就是舊的資料。

      ​ 新增UserMapper1.xml ,修改命名空間為com.rangers.dao.IUserDao1

      InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
      SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
      
      User param = new User();
      param.setId(1);
      
      SqlSession sqlSession1 = sqlSessionFactory.openSession();
      IUserDao userDao1 = sqlSession1.getMapper(IUserDao.class);
      User user1 = userDao1.findOne(param);
      System.out.println("sqlSession1查詢結果:"+user1);
      sqlSession1.close();
      
      SqlSession sqlSession2 = sqlSessionFactory.openSession();
      param.setName("遊騎兵");
      param.setAddress("西安");
      Boolean flag = sqlSession2.update("com.rangers.dao.IUserDao1.updateUser",param) > 0;
      System.out.println("執行更新操作結果:"+flag);
      sqlSession2.commit();
      sqlSession2.close();
      
      SqlSession sqlSession3 = sqlSessionFactory.openSession();
      IUserDao userDao3 = sqlSession3.getMapper(IUserDao.class);
      User user3 = userDao3.findOne(param);
      sqlSession3.close();
      System.out.println("sqlSession3查詢結果:"+user3);
                 
      ​ 檢視控制台輸出,發現sqlSession2成功更新使用者資訊後,sqlSession3進行查詢時二級緩存命中,依舊是sqlSession1中的結果,查詢結果錯誤
      15:33:54,792 DEBUG JdbcTransaction:137 - Opening JDBC Connection
      15:33:55,164 DEBUG PooledDataSource:406 - Created connection 1165303897.
      15:33:55,165 DEBUG JdbcTransaction:101 - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@45752059]
      15:33:55,168 DEBUG findOne:159 - ==>  Preparing: select * from user where id=? 
      15:33:55,186 DEBUG findOne:159 - ==> Parameters: 1(Integer)
      15:33:55,212 DEBUG findOne:159 - <==      Total: 1
      sqlSession1查詢結果:com.rangers.entity.User{id=1, name='Rangers', address='杭州'}
      15:33:55,215 DEBUG JdbcTransaction:123 - Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@45752059]
      15:33:55,218 DEBUG JdbcTransaction:91 - Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@45752059]
      15:33:55,218 DEBUG PooledDataSource:363 - Returned connection 1165303897 to pool.
      15:33:55,219 DEBUG JdbcTransaction:137 - Opening JDBC Connection
      15:33:55,220 DEBUG PooledDataSource:398 - Checked out connection 1165303897 from pool.
      15:33:55,220 DEBUG JdbcTransaction:101 - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@45752059]
      15:33:55,224 DEBUG updateUser:159 - ==>  Preparing: update user set name=?,address=? where id=? 
      15:33:55,225 DEBUG updateUser:159 - ==> Parameters: 遊騎兵(String), 西安(String), 1(Integer)
      15:33:55,233 DEBUG updateUser:159 - <==    Updates: 1
      執行更新操作結果:true
      15:33:55,233 DEBUG JdbcTransaction:70 - Committing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@45752059]
      15:33:55,351 DEBUG JdbcTransaction:123 - Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@45752059]
      15:33:55,354 DEBUG JdbcTransaction:91 - Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@45752059]
      15:33:55,355 DEBUG PooledDataSource:363 - Returned connection 1165303897 to pool.
      15:33:55,357 DEBUG IUserDao:62 - Cache Hit Ratio [com.rangers.dao.IUserDao]: 0.5
      sqlSession3查詢結果:com.rangers.entity.User{id=1, name='Rangers', address='杭州'}
      
                 
      1. 解決

        在查詢的Mapper檔案中引入cache-ref标簽,指向執行增删改操作表的命名空間,例如在UserMapper.xml中增加一下标簽

        <cache-ref namespace="com.rangers.dao.IUserDao1"/>
                   
        在此執行,檢視控制台輸出,發現第三次查詢已直接從資料庫中進行查詢,結果正确
        15:46:05,486 DEBUG IUserDao1:62 - Cache Hit Ratio [com.rangers.dao.IUserDao1]: 0.0
        15:46:05,489 DEBUG JdbcTransaction:137 - Opening JDBC Connection
        15:46:05,788 DEBUG PooledDataSource:406 - Created connection 1165303897.
        15:46:05,789 DEBUG JdbcTransaction:101 - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@45752059]
        15:46:05,793 DEBUG findOne:159 - ==>  Preparing: select * from user where id=? 
        15:46:05,822 DEBUG findOne:159 - ==> Parameters: 1(Integer)
        15:46:05,842 DEBUG findOne:159 - <==      Total: 1
        sqlSession1查詢結果:com.rangers.entity.User{id=1, name='rangers', address='杭州'}
        15:46:05,845 DEBUG JdbcTransaction:123 - Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@45752059]
        15:46:05,848 DEBUG JdbcTransaction:91 - Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@45752059]
        15:46:05,848 DEBUG PooledDataSource:363 - Returned connection 1165303897 to pool.
        15:46:05,849 DEBUG JdbcTransaction:137 - Opening JDBC Connection
        15:46:05,849 DEBUG PooledDataSource:398 - Checked out connection 1165303897 from pool.
        15:46:05,849 DEBUG JdbcTransaction:101 - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@45752059]
        15:46:05,852 DEBUG updateUser:159 - ==>  Preparing: update user set name=?,address=? where id=? 
        15:46:05,852 DEBUG updateUser:159 - ==> Parameters: 遊騎兵(String), 西安(String), 1(Integer)
        15:46:05,865 DEBUG updateUser:159 - <==    Updates: 1
        執行更新操作結果:true
        15:46:05,865 DEBUG JdbcTransaction:70 - Committing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@45752059]
        15:46:05,970 DEBUG JdbcTransaction:123 - Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@45752059]
        15:46:05,974 DEBUG JdbcTransaction:91 - Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@45752059]
        15:46:05,976 DEBUG PooledDataSource:363 - Returned connection 1165303897 to pool.
        15:46:05,976 DEBUG IUserDao1:62 - Cache Hit Ratio [com.rangers.dao.IUserDao1]: 0.0
        15:46:05,976 DEBUG JdbcTransaction:137 - Opening JDBC Connection
        15:46:05,976 DEBUG PooledDataSource:398 - Checked out connection 1165303897 from pool.
        15:46:05,976 DEBUG JdbcTransaction:101 - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@45752059]
        15:46:05,979 DEBUG findOne:159 - ==>  Preparing: select * from user where id=? 
        15:46:05,979 DEBUG findOne:159 - ==> Parameters: 1(Integer)
        15:46:05,985 DEBUG findOne:159 - <==      Total: 1
        15:46:05,985 DEBUG JdbcTransaction:123 - Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@45752059]
        15:46:05,989 DEBUG JdbcTransaction:91 - Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@45752059]
        15:46:05,990 DEBUG PooledDataSource:363 - Returned connection 1165303897 to pool.
        sqlSession3查詢結果:com.rangers.entity.User{id=1, name='遊騎兵', address='西安'}
                   
    • 是否應該使用二級緩存
      二級緩存的注意事項:
      1. 緩存是以

        namespace

        為機關的,不同

        namespace

        下的操作互不影響。
      2. insert,update,delete操作會清空所在

        namespace

        下的全部緩存。
      3. 通常使用MyBatis Generator生成的代碼中,都是各個表獨立的,每個表都有自己的

        namespace

      4. 多表操作一定不要使用二級緩存,因為多表操作進行更新操作,一定會産生髒資料。

        如果你遵守二級緩存的注意事項,那麼你就可以使用二級緩存。

        如果不能使用多表操作,二級緩存不就可以用一級緩存來替換掉嗎?而且二級緩存是表級緩存,開銷大,沒有一級緩存直接使用 HashMap 來存儲的效率更高,是以二級緩存并不推薦使用。

    引用内容參考:https://www.cnblogs.com/cxuanBlog/p/11333021.html