MyBatis筆記(五)——一級緩存,二級緩存
文章目錄
參考: https://www.bilibili.com/video/BV1NE411Q7Nx
視頻原作者CSDN: https://blog.csdn.net/qq_33369905
這篇是自己整理了一下,以便自己複習。
1.前期準備
1.1 緩存簡介
1、什麼是緩存 [ Cache ]?
- 存在內存中的臨時數據。
2、爲什麼使用緩存?
- 減少和數據庫的交互次數,減少系統開銷,提高系統效率。
3、什麼樣的數據能使用緩存?
- 經常查詢並且不經常改變的數據。
合理利用緩存可提高系統運行效率!
1.2 MyBatis中的自帶緩存簡介
MyBatis系統中默認定義了兩級緩存:一級緩存和二級緩存
- 一級緩存:默認開啓 SqlSession(對一個會話中的數據進行緩存)
- 二級緩存:手動開啓和配置,基於某個xml映射文件的緩存
此外提供了緩存接口Cache,可自定義二級緩存。
1.3 數據表與實體類準備
-- ----------------------------
-- Table structure for `user`
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int(20) NOT NULL,
`name` varchar(30) DEFAULT NULL,
`pwd` varchar(30) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES ('1', '張三', '1123');
INSERT INTO `user` VALUES ('2', '李四', 'ksqo');
INSERT INTO `user` VALUES ('3', '王五', 'wwww');
User類
public class User {
private int id; //id
private String name; //姓名
private String pwd; //密碼
//推薦Lombok
//構造,有參,無參
//set/get
//toString()
}
2.MyBatis中的一級緩存
2.1 一級緩存效果
添加log4j日誌以便查看執行的SQL。
此不贅述,可查看:MyBatis筆記(三)——ResultMap結果集映射,日誌,分頁的多種實現方式
UserMapper接口類:
public interface UserMapper {
//查詢所有User
List<User> selectUser();
//按照id查詢User
User selectUserById(int id);
}
UserMapper.xml
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.piao.dao.UserMapper">
<select id="selectUser" resultType="com.piao.User">
select * from user;
</select>
<select id="selectUserById" parameterType="int" resultType="com.piao.User">
select * from user where id = #{id};
</select>
</mapper>
測試:
@Test
public void testSelectUserById() {
System.out.println("測試按照id查詢用戶selectUserById()");
List<User> users = mapper.selectUser();
for (User user : users) {
System.out.println(user);
}
List<User> users2 = mapper.selectUser();
for (User user : users) {
System.out.println(user);
}
//比較兩次 查詢所有 的結果集
System.out.println(users == users2);
User user = mapper.selectUserById(2);
System.out.println(user);
User user2 = mapper.selectUserById(2);
System.out.println(user);
//比較兩次 按照id查詢 的結果集
System.out.println(user == user2);
}
輸出:可看到第二次查詢所有用戶和第二次查詢id爲2的用戶沒有與數據庫交互,沒有執行SQL。
這便是MyBatis默認開啓的一級緩存的效果,對於單條查詢SQL語句的結果,sqlsession會保存該數據。
DEBUG [main] - ==> Preparing: select * from user;
DEBUG [main] - ==> Parameters:
TRACE [main] - <== Columns: id, name, pwd
TRACE [main] - <== Row: 1, 張三, 1123
TRACE [main] - <== Row: 2, 李四, ksqo
TRACE [main] - <== Row: 3, 王五, wwww
DEBUG [main] - <== Total: 3
User{id=1, name='張三', pwd='1123'}
User{id=2, name='李四', pwd='ksqo'}
User{id=3, name='王五', pwd='wwww'}
User{id=1, name='張三', pwd='1123'}
User{id=2, name='李四', pwd='ksqo'}
User{id=3, name='王五', pwd='wwww'}
true
DEBUG [main] - ==> Preparing: select * from user where id = ?;
DEBUG [main] - ==> Parameters: 2(Integer)
TRACE [main] - <== Columns: id, name, pwd
TRACE [main] - <== Row: 2, 李四, ksqo
DEBUG [main] - <== Total: 1
User{id=2, name='李四', pwd='ksqo'}
User{id=2, name='李四', pwd='ksqo'}
true
2.2 MyBatis一級緩存失效的四種情況
MyBatis一級緩存失效結果就是MyBatis會與數據庫交互,刷新一級緩存。
- sqlSession不同
每個sqlSession中保存的數據是不共享的,相互獨立。因此使用不同的sqlSession對象去獲取mapper來查詢一摸一樣的數據,仍然會與數據庫交互。
- sqlSession相同,查詢條件不同
剛剛上述的運行結果正驗證了這一點,一個查詢所有,一個按照id=2查詢,分別執行了SQL,後面一級緩存便更新成了按照id=2查詢的結果。
- sqlSession相同,兩次查詢之間執行了增刪改操作
//測試按照id查詢用戶selectUserById()
@Test
public void testSelectUserById() {
System.out.println("測試按照id查詢用戶selectUserById()");
User user = mapper.selectUserById(2);
System.out.println(user);
User user3 = new User(5, "趙六", "2333");
mapper.insertUser(user3);
session.commit();//提交事務 原生jdbc則需要開啓事務執行sql後提交事務
User user2 = mapper.selectUserById(2);
System.out.println(user);
//比較兩次 按照id查詢 的結果集
System.out.println(user == user2);
}
輸出,重新查詢了數據庫
此時緩存已失效,因爲增刪改可能對數據產生影響,造成數據庫與緩存不一致的可能。
DEBUG [main] - ==> Preparing: select * from user where id = ?;
DEBUG [main] - ==> Parameters: 2(Integer)
TRACE [main] - <== Columns: id, name, pwd
TRACE [main] - <== Row: 2, 李四, ksqo
DEBUG [main] - <== Total: 1
User{id=2, name='李四', pwd='ksqo'}
DEBUG [main] - ==> Preparing: insert into user(id,name,pwd) values (?,?,?);
DEBUG [main] - ==> Parameters: 5(Integer), 趙六(String), 2333(String)
DEBUG [main] - <== Updates: 1
DEBUG [main] - ==> Preparing: select * from user where id = ?;
DEBUG [main] - ==> Parameters: 2(Integer)
TRACE [main] - <== Columns: id, name, pwd
TRACE [main] - <== Row: 2, 李四, ksqo
DEBUG [main] - <== Total: 1
User{id=2, name='李四', pwd='ksqo'}
false
- sqlSession相同,手動清除一級緩存
session.clearCache();//手動清除緩存
2.3 一級緩存流程圖
圖源百度
3.MyBatis中的二級緩存
由上述可看到MyBatis中一級緩存的侷限性,條件太苛刻(很容易失效),性能提升十分有限。
MyBatis中的二級緩存(也稱全局緩存) 基於namespace級別的緩存,一個名稱空間,對應一個二級緩存(全局緩存);
3.1開啓二級緩存(簡要版)
要啓用全局的二級緩存,只需要在你的 SQL 映射文件(***Mapper.xml)中添加:
<!--當前mapper.xml中使用二級緩存-->
<cache/>
然後在MyBatis-config.xml中的settings添加全局開啓緩存
<settings>
<!-- 開啓緩存(默認開啓)-->
<setting name="cacheEnabled" value="true"/>
</settings>
官方文檔有對應的說明(其實默認就是開啓的):
注意這個設置是: 全局性地開啓或關閉所有映射器配置文件中已配置的任何緩存。
也就是說,沒有加cache元素的xml映射文件依舊是默認的一級緩存!
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-82uQBNa1-1589514753471)(D:\博客markdown文件\CSDN\應用框架\MyBatis\MyBatis筆記(五)]——緩存.assets\image-20200515104647231.png)
3.2 二級緩存效果與工作機制
這個簡單語句的效果如下:
- 映射語句文件中的所有 select 語句的結果將會被緩存。
- 映射語句文件中的所有 insert、update 和 delete 語句會刷新緩存。
- 緩存會使用最近最少使用算法(LRU, Least Recently Used)算法來清除不需要的緩存。
- 緩存不會定時進行刷新(也就是說,沒有刷新間隔)。
- 緩存會保存列表或對象(無論查詢方法返回哪種)的 1024 個引用。
- 緩存會被視爲讀/寫緩存,這意味着獲取到的對象並不是共享的,可以安全地被調用者修改,而不干擾其他調用者或線程所做的潛在修改。
工作機制:
- 在一級緩存的基礎上如果會話關閉了,一級緩存的數據就會被保存到二級緩存中
- 新的會話查詢信息可以從二級緩存裏獲取內容**(如果一級緩存沒有的話)**
- 不同mapper之間的數據是獨立的**(即二級緩存的作用域是namespace級別,存在各自的map中)**
3.3 自定義設置二級緩存cache元素屬性
這些屬性可以通過 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。
提示 二級緩存是事務性的。這意味着,當 SqlSession 完成並提交時,或是完成並回滾,但沒有執行 flushCache=true 的 insert/delete/update 語句時,緩存會獲得更新。
3.4 具體的mapper映射xml文件編寫舉例
在MyBatis-config.xml中的settings添加全局開啓緩存(其實默認就是開啓的):
<settings>
<!-- 開啓緩存(默認開啓)-->
<setting name="cacheEnabled" value="true"/>
</settings>
UserMapper接口:
//操作數據庫的接口
public interface UserMapper {
//查詢所有User
List<User> selectUser();
//按照id查詢User
User selectUserById(int id);
}
UserMapeer.xml
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace綁定一個對應的Mapper接口-->
<mapper namespace="com.piao.dao.UserMapper">
<!--當前mapper.xml中使用二級緩存
eviction="FIFO" 清除策略選擇先進先出
flushInterval="60000" 每隔60s刷新
size="512" 最多可以存儲結果對象或列表的 512 個引用,
readOnly="true" 返回的對象被認爲是隻讀的
-->
<cache eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
<!-- parameterType參數類型-->
<!-- 傳參方式: #{id} id爲selectUserById方法中的int類型參數id selectUserById(int id);-->
<select id="selectUserById" parameterType="int" resultType="com.piao.User">
select * from user where id = #{id};
</select>
</mapper>
測試
//測試二級緩存
@Test
public void testSelectUserById() {
SqlSession session = MybatisUtils.getSession();
User5Mapper mapper = session.getMapper(User5Mapper.class);
SqlSession session2 = MybatisUtils.getSession();
User5Mapper mapper2 = session2.getMapper(User5Mapper.class);
SqlSession session3 = MybatisUtils.getSession();
User5Mapper mapper3 = session3.getMapper(User5Mapper.class);
User user = mapper.selectUserById(2);
System.out.println(user);
session.close();//關閉第一個sqlsession 會將一級緩存提交到二級緩存
User user2 = mapper2.selectUserById(2);
System.out.println(user);
//比較兩次 按照id查詢 的結果集
System.out.println(user == user2);
session2.close();
User user3 = mapper3.selectUserById(2);
System.out.println(user);
//比較兩次 按照id查詢 的結果集
System.out.println(user == user3);
session3.close();
}
測試輸出:可看到第一個SsqlSession關閉後將一級緩存提交到二級緩存,後面兩個sqlsession從二級緩存中拿到id=2的數據,三次查詢出的結果集User都是同一個。
同時可看到日式的輸出有Cache Hit Ratio 緩存命中率:
命中率=從緩存中讀取數據的次數/所有訪問數據次數(磁盤讀取次數+緩存讀取次數)
第一次是0,因爲二級緩存裏沒有數據。第二次爲0.5,因爲第二次是從緩存拿到的數據,第三次是0.66666,說明也是從緩存中拿到的數據。就是隻有第一次是從MySQL讀取(磁盤讀取),整個過程只執行一次SQL
DEBUG [main] - Cache Hit Ratio [com.piao.dao.User5Mapper]: 0.0
DEBUG [main] - ==> Preparing: select * from user where id = ?;
DEBUG [main] - ==> Parameters: 2(Integer)
TRACE [main] - <== Columns: id, name, pwd
TRACE [main] - <== Row: 2, 李四, ksqo
DEBUG [main] - <== Total: 1
User{id=2, name='李四', pwd='ksqo'}
DEBUG [main] - Cache Hit Ratio [com.piao.dao.User5Mapper]: 0.5
User{id=2, name='李四', pwd='ksqo'}
true
DEBUG [main] - Cache Hit Ratio [com.piao.dao.User5Mapper]: 0.6666666666666666
User{id=2, name='李四', pwd='ksqo'}
true
結果也驗證說明二級緩存是跨sqlsession的,作用域是namespace級別!
3.5 二級緩存流程圖
圖源百度
先走一級緩存,(如果開啓二級緩存的話)再走二級緩存,(如果有第三方緩存)再走第三方緩存,沒有就走數據庫