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。
- 總結
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 測試
- 實現類 => 【實現Serializable接口】
@Data
//實現二級緩存返回的pojo對象必須要求是安全的。
//由於二級緩存的數據不一定都是存儲到內存中,它的存儲介質多種多樣,所以需要給緩存的對象執行序列化。如果存儲在內存中的話,實測不序列化也可以的。
public class User implements Serializable {
private int id;
private String name;
private String pwd;
}
-
XML配置文件 => 【開啟二級緩存】
- 默認配置
<cache/>
- 自定義配置
<cache
eviction="FIFO"
flushInterval="3000"
size="512"
readOnly="true"/>
- 測試操作
@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();
}
第一次先不開啟二級緩存
- 運行結果如下:可以看出再不開啟二級緩存的情況下,兩個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
第二次開啟二級緩存
- 結果如下:可以看出開啟二級緩存後查詢結果兩個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
注意如果不進行緩存只讀設置很可能會刷新第一次查詢的緩存記錄
- 根據下方的測試結果一,可以看出我們不設置readOnly=”true”時,返回的user01,user02和user03都是不同的,
二級緩存是事務性的。這意味着,當 SqlSession 完成並提交時,或是完成並回滾,但沒有執行 flushCache=true 的 insert/delete/update 語句時,緩存會獲得更新。
- 根據下方的測試結果二,可以看出我們設置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對象。