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對象。