mybatis緩存-二級緩存

1.2 二級緩存

  • 【官方聲明】 => 如何開啟【二級緩存】

    默認情況下,只啟用了本地的會話緩存,它僅僅對一個會話中的數據進行緩存。 要啟用全局的二級緩存,只需要在你的 SQL 映射文件中添加一行:

    • 在XML映射文件中添加以下代碼,以開啟【二級緩存】
    <cache/>
  • 【官方聲明】 => 【二級緩存】的作用

    • 映射語句文件中的所有 select 語句的結果將會被緩存。
    • 映射語句文件中的所有 insert、update 和 delete 語句會刷新緩存。
    • 緩存會使用最近最少使用算法(LRU, Least Recently Used)算法來清除不需要的緩存。
    • 緩存不會定時進行刷新(也就是說,沒有刷新間隔)。
    • 緩存會保存列表或對象(無論查詢方法返回哪種)的 1024 個引用。
    • 緩存會被視為讀/寫緩存,這意味着獲取到的對象並不是共享的,可以安全地被調用者修改,而不干擾其他調用者或線程所做的潛在修改。
  • 【官方提示】 => 【二級緩存】的作用域

    • 緩存只作用於 cache 標籤所在的映射文件中的語句。如果你混合使用 Java API 和 XML 映射文件,在共用接口中的語句將不會被默認緩存。你需要使用 @CacheNamespaceRef 註解指定緩存作用域。
  • 【官方聲明】 => <cache>標籤的屬性修改

  <cache
    eviction="FIFO"
    flushInterval="60000"
    size="512"
    readOnly="true"/>

這個更高級的配置創建了一個 FIFO 緩存,每隔 60 秒刷新,最多可以存儲結果對象或列表的 512 個引用,而且返回的對象被認為是只讀的,因此對它們進行修改可能會在不同線程中的調用者產生衝突。

可用的清除策略有:

  • LRU – 最近最少使用:移除最長時間不被使用的對象。
  • FIFO – 先進先出:按對象進入緩存的順序來移除它們。
  • SOFT – 軟引用:基於垃圾回收器狀態和軟引用規則移除對象。
  • WEAK – 弱引用:更積極地基於垃圾收集器狀態和弱引用規則移除對象。

默認的清除策略是 LRU。

flushInterval(刷新間隔)屬性可以被設置為任意的正整數,設置的值應該是一個以毫秒為單位的合理時間量。 默認情況是不設置,也就是沒有刷新間隔,緩存僅僅會在調用語句時刷新。

size(引用數目)屬性可以被設置為任意正整數,要注意欲緩存對象的大小和運行環境中可用的內存資源。默認值是 1024。

readOnly(只讀)屬性可以被設置為 true 或 false。只讀的緩存會給所有調用者返回緩存對象的相同實例。 因此這些對象不能被修改。這就提供了可觀的性能提升。而可讀寫的緩存會(通過序列化)返回緩存對象的拷貝。 速度上會慢一些,但是更安全,因此默認值是 false。

  • 總結
    image

1.2.1 什麼是二級緩存?

MyBatis的二級緩存是Application級別的緩存,它可以提高對數據庫查詢的效率,以提高應用的性能。

二級緩存即使當一級緩存被清除/關閉也會存在(即sqlsession.close()方法執行後依舊會保存查詢緩存)

SqlSessionFactory層面上的二級緩存默認是不開啟的,二級緩存的開啟需要進行配置,實現二級緩存的時候,MyBatis要求返回的POJO必須是可序列化的( 要求實現Serializable接口)

1.2.2 二級緩存的作用

  • 映射語句文件中的所有select語句將會被緩存。
  • 映射語句文件中的所有insert、update和delete語句會刷新緩存。
  • 緩存會使用默認的Least Recently Used(LRU,最近最少使用的)算法來收回。
  • 根據時間表,比如No Flush Interval,(CNFI沒有刷新間隔),緩存不會以任何時間順序來刷新。
  • 緩存會存儲列表集合或對象(無論查詢方法返回什麼)的1024個引用
  • 緩存會被視為是read/write(可讀/可寫)的緩存,意味着對象檢索不是共享的,而且可以安全的被調用者修改,不干擾其他調用者或線程所做的潛在修改。

1.2.3 測試

  1. 實現類 => 【實現Serializable接口】
@Data
//實現二級緩存返回的pojo對象必須要求是安全的。
//由於二級緩存的數據不一定都是存儲到內存中,它的存儲介質多種多樣,所以需要給緩存的對象執行序列化。如果存儲在內存中的話,實測不序列化也可以的。
public class User implements Serializable {
    private int id;
    private String name;
    private String pwd;
}
  1. XML配置文件 => 【開啟二級緩存】

    • 默認配置
     <cache/>
  • 自定義配置
     <cache
           eviction="FIFO" 
           flushInterval="3000"
           size="512"
           readOnly="true"/>
  1. 測試操作
    @Test
    public void Test03(){
        SqlSession sqlSession01 = MybatisUtils.getSqlSession();
        SqlSession sqlSession02 = MybatisUtils.getSqlSession();
        UserMapper mapper01 = sqlSession01.getMapper(UserMapper.class);
        User user01 = mapper01.queryUserById(2);
        System.out.println(user01.toString());
        sqlSession01.close();
        System.out.println("------------***************----------------");
        UserMapper mapper02 = sqlSession02.getMapper(UserMapper.class);
        User user02 = mapper02.queryUserById(2);
        System.out.println(user02.toString());
        System.out.println(user01==user02);
        sqlSession02.close();
    }

第一次先不開啟二級緩存
image

  • 運行結果如下:可以看出再不開啟二級緩存的情況下,兩個sqlsession產生的結果對象並不一致,且查詢操作sql語句使用了兩次
Logging initialized using 'class org.apache.ibatis.logging.stdout.StdOutImpl' adapter.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
Opening JDBC Connection
Created connection 2130772866.
==>  Preparing: select * from mybatis.user where id=?
==> Parameters: 2(Integer)
<==    Columns: id, name, pwd
<==        Row: 2, lisi, 123456
<==      Total: 1
User(id=2, name=lisi, pwd=123456)
Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@7f010382]
Returned connection 2130772866 to pool.
------------***************----------------
Opening JDBC Connection
Checked out connection 2130772866 from pool.
==>  Preparing: select * from mybatis.user where id=?
==> Parameters: 2(Integer)
<==    Columns: id, name, pwd
<==        Row: 2, lisi, 123456
<==      Total: 1
User(id=2, name=lisi, pwd=123456)
false
Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@7f010382]
Returned connection 2130772866 to pool.

進程已結束,退出代碼0

第二次開啟二級緩存
image

  • 結果如下:可以看出開啟二級緩存後查詢結果兩個sqlsession產生的返回對象是同一個,且sql語句只調用了一次
Logging initialized using 'class org.apache.ibatis.logging.stdout.StdOutImpl' adapter.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
Cache Hit Ratio [com.zhang.dao.UserMapper]: 0.0
Opening JDBC Connection
Created connection 963110412.
==>  Preparing: select * from mybatis.user where id=?
==> Parameters: 2(Integer)
<==    Columns: id, name, pwd
<==        Row: 2, lisi, 123456
<==      Total: 1
User(id=2, name=lisi, pwd=123456)
Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@3967e60c]
Returned connection 963110412 to pool.
------------***************----------------
Cache Hit Ratio [com.zhang.dao.UserMapper]: 0.5
User(id=2, name=lisi, pwd=123456)
true

進程已結束,退出代碼0

注意如果不進行緩存只讀設置很可能會刷新第一次查詢的緩存記錄
image

  • 根據下方的測試結果一,可以看出我們不設置readOnly=”true”時,返回的user01,user02和user03都是不同的,

二級緩存是事務性的。這意味着,當 SqlSession 完成並提交時,或是完成並回滾,但沒有執行 flushCache=true 的 insert/delete/update 語句時,緩存會獲得更新。
image

  • 根據下方的測試結果二,可以看出我們設置readOnly=”true”時,返回的user01和user02是相同的,但user02和user03是不同的,
       @Test
    public void Test03(){
        SqlSession sqlSession01 = MybatisUtils.getSqlSession();
        SqlSession sqlSession02 = MybatisUtils.getSqlSession();
        UserMapper mapper01 = sqlSession01.getMapper(UserMapper.class);
        User user01 = mapper01.queryUserById(2);
        System.out.println(user01.toString());
        sqlSession01.close();

        System.out.println("------------***************----------------");
        UserMapper mapper02 = sqlSession02.getMapper(UserMapper.class);
        User user02 = mapper02.queryUserById(2);
        System.out.println(user02.toString());
        System.out.println(user01==user02);
        User user = new User();
        user.setId(3);
        user.setPwd("6113081");
        user.setName("FT");
        int i = mapper02.updateUser(user);
        if (i >= 0) {
            System.out.println("更新成功");
            sqlSession02.commit();//事務一旦提交就會刷新緩存
        }else{
            System.out.println("更新失敗");
            sqlSession02.close();
        }
        User user03 = mapper02.queryUserById(2);
        System.out.println(user03==user02);
        sqlSession02.close();
    }
測試結果一
Logging initialized using 'class org.apache.ibatis.logging.stdout.StdOutImpl' adapter.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
Cache Hit Ratio [com.zhang.dao.UserMapper]: 0.0
Opening JDBC Connection
Created connection 2061347276.
==>  Preparing: select * from mybatis.user where id=?
==> Parameters: 2(Integer)
<==    Columns: id, name, pwd
<==        Row: 2, lisi, 123456
<==      Total: 1
User(id=2, name=lisi, pwd=123456)
Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@7adda9cc]
Returned connection 2061347276 to pool.
------------***************----------------
As you are using functionality that deserializes object streams, it is recommended to define the JEP-290 serial filter. Please refer to //docs.oracle.com/pls/topic/lookup?ctx=javase15&id=GUID-8296D8E8-2B93-4B9A-856E-0A65AF9B8C66
Cache Hit Ratio [com.zhang.dao.UserMapper]: 0.5
User(id=2, name=lisi, pwd=123456)
false
Opening JDBC Connection
Checked out connection 2061347276 from pool.
==>  Preparing: update mybatis.user set name=?,pwd=? where id=?
==> Parameters: FT(String), 6113081(String), 3(Integer)
<==    Updates: 1
更新成功
Cache Hit Ratio [com.zhang.dao.UserMapper]: 0.3333333333333333
==>  Preparing: select * from mybatis.user where id=?
==> Parameters: 2(Integer)
<==    Columns: id, name, pwd
<==        Row: 2, lisi, 123456
<==      Total: 1
false
Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@7adda9cc]
Returned connection 2061347276 to pool.

進程已結束,退出代碼0
測試結果二
Logging initialized using 'class org.apache.ibatis.logging.stdout.StdOutImpl' adapter.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
Cache Hit Ratio [com.zhang.dao.UserMapper]: 0.0
Opening JDBC Connection
Created connection 963110412.
==>  Preparing: select * from mybatis.user where id=?
==> Parameters: 2(Integer)
<==    Columns: id, name, pwd
<==        Row: 2, lisi, 123456
<==      Total: 1
User(id=2, name=lisi, pwd=123456)
Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@3967e60c]
Returned connection 963110412 to pool.
------------***************----------------
Cache Hit Ratio [com.zhang.dao.UserMapper]: 0.5
User(id=2, name=lisi, pwd=123456)
true
Opening JDBC Connection
Checked out connection 963110412 from pool.
==>  Preparing: update mybatis.user set name=?,pwd=? where id=?
==> Parameters: FT(String), 6113081(String), 3(Integer)
<==    Updates: 1
更新成功
Cache Hit Ratio [com.zhang.dao.UserMapper]: 0.3333333333333333
==>  Preparing: select * from mybatis.user where id=?
==> Parameters: 2(Integer)
<==    Columns: id, name, pwd
<==        Row: 2, lisi, 123456
<==      Total: 1
false
Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@3967e60c]
Returned connection 963110412 to pool.

進程已結束,退出代碼0

1.2.4 小結

二級緩存是mapper級別的緩存,它的實現機制跟一級緩存差不多,也是基於PerpetualCache的HashMap本地存儲。作用域為mapper的namespace,可以自定義存儲,比如Ehcache。Mybatis的二級緩存是跨Session的,每個Mapper享有同一個二級緩存域。
Mybatis內部存儲緩存使用一個HashMap,key為hashCode+sqlId+Sql語句。value為從查詢出來映射生成的Java對象。