緩存
緩存的作用就是方便快速的查詢,將從數據庫中查詢出來的經常使用並且不經常改變的數據放在內存中,這樣更有助於用戶的快速查詢,這樣也能減少數據庫和服務器的壓力。
Mybatis中提供了兩種緩存機制,Mybatis默認是開啓緩存的,而且它的默認緩存機制是以及緩存
- 一級緩存
- 二級緩存
當然,Mybatis也支持自定義緩存機制
一級緩存
一級緩存是Mybatis默認的緩存機制,該緩存存在於一個SqlSession的生命週期中。
感覺畫的這個圖有點更復雜了。。。。
博主來解釋一下吧:
就是說,當我們獲得一個SqlSession之後,我們利用該SqlSession去進行查詢,查詢出來的內容會被Mybatis自動的放到這個一級緩存中,直到該SqlSession被關閉,在這段過程中,就是一級緩存的生命週期。在這段時間內進行的查詢,Mybatis會先從緩存中查找,如果緩存中沒有再查詢數據庫,然後將從數據庫中查詢出來的數據放入緩存,如果緩存中有,就直接查詢出緩存中的數據,從而減少對數據庫的操作次數,從一定程度上提升了程序的性能。
在緩存的正常生命週期中,有以下幾種方式會讓緩存失效:
- 在這期間SqlSession進行了增刪改操作
- 手動銷燬緩存
sqlSession.clearCache();
我們直接舉一個例子吧
比如我們需要從數據庫中查詢一個指定ID的用戶:
@Test
public void getUserByIdTest() throws InterruptedException {
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
//查詢id爲1的用戶
User user1 = userMapper.getUserById(1);
System.out.println(user1);
Thread.sleep(1000);
System.out.println("============");
//再次查詢id爲1的用戶
User user2 = userMapper.getUserById(1);
System.out.println(user2);
System.out.println(user1 == user2);
sqlSession.close();
}
上面的代碼,按照我們的理解,它肯定會去操作兩次數據庫,其實並不是,下面展示運行時的日誌輸出:
這也說明了我們執行第二次查詢的時候是直接從內存中操作,而且查詢出來的和第一次查詢結果一致。
這裏再提供一個對比的測試代碼:
@Test
public void getUserByIdTest() throws InterruptedException {
SqlSession sqlSession1 = MybatisUtils.getSqlSession();
UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
User user1 = userMapper1.getUserById(1);
System.out.println(user1);
//=====================================
Thread.sleep(1000);
System.out.println("============");
//=====================================
SqlSession sqlSession2 = MybatisUtils.getSqlSession();
UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
User user2 = userMapper2.getUserById(1);
System.out.println(user2);
System.out.println(user1 == user2);
sqlSession1.close();
sqlSession2.close();
}
這個代碼就模擬兩個不同的SqlSession進行查詢,結果如下:
我們明顯的發現,它就執行了兩次SQL語句。
二級緩存
二級緩存只針對於一個Mapper文件,它的啓動方式就是在SQL映射文件中添加一行:
<cache/>
以上簡單語句的效果如下:
- 開啓二級緩存之後,該映射文件中的所有select語句的結果都會被緩存
- 該映射文件中所有的增刪改語句都會刷新緩存
- 默認的二級緩存會使用最近最少使用算法來清除不需要的緩存
- 該緩存會不定時的進行刷新
- 該緩存會保存列表或對象的1024個引用
- 緩存會被視爲讀/寫緩存,這意味着獲取到的對象並不是共享的,可以安全地被調用者修改,而不干擾其他調用者或線程所做的潛在修改。
這個<cache>
標籤含有如下屬性:
- eviction:指定清除策略
- LRU:最近最少使用,移除最長時間不被使用的對象(默認)
- FIFO:先進先出,按對象進入緩存的順序來移除它們
- SOFT:軟引用,基於垃圾回收器狀態和軟引用規則來移除對象
- WEAK:弱引用,更積極的基於垃圾收集器狀態和弱引用規則移除對象
- flushInterval:指定刷新間隔,單位毫秒
- size:指定緩存中可以放置的最大對象數
- readOnly:是否只讀。
- true:只讀的緩存會給所有調用者返回緩存對象的相同實例,因此這些對象不能被修改,這就提供了可觀的性能提升
- false:可讀寫的緩存會通過序列化返回緩存對象的拷貝,速度相對來說慢一點,但是更加安全,默認問false
注意:二級緩存是事務性的。這意味着,當 SqlSession 完成並提交時,或是完成並回滾,但沒有執行 flushCache=true 的 insert/delete/update 語句時,緩存會獲得更新。
我們也來測試一波:
先在Mapper.xml中開啓默認二級緩存,及添加<cache/>
即可
然後測試代碼修改如下:
@Test
public void getUserByIdTest() throws InterruptedException {
SqlSession sqlSession1 = MybatisUtils.getSqlSession();
UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
User user1 = userMapper1.getUserById(1);
System.out.println(user1);
sqlSession1.close();
//=====================================
Thread.sleep(1000);
System.out.println("============");
//=====================================
SqlSession sqlSession2 = MybatisUtils.getSqlSession();
UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
User user2 = userMapper2.getUserById(1);
System.out.println(user2);
System.out.println(user1 == user2);
sqlSession2.close();
}
測試結果:
這裏需要注意的是,需要前一個SqlSession完成提交、回滾或者關閉之後,二級緩存纔會更新。
還有個需要注意的地方,如果實體類沒有被序列化,會出現報錯,序列化實體類如下:
package com.ara.pojo;
import java.io.Serializable;
public class User implements Serializable {
private int id;
private String name;
private String password;
}
這樣開啓二級緩存才能夠正常使用。
總結:
- 只要開啓了二級緩存,就在一個Mapper中有效。
- 查詢出來的結果會先放在一級緩存中,當SqlSession提交、回滾或者關閉後纔會存到二級緩存中。
- 如要使用二級緩存,需要將實體類序列化,否則會報錯
一級緩存和二級緩存的關係:
實際查詢調用中:
- 它會先查看二級緩存中是否存在對應數據
- 有,就直接返回
- 沒有就查看一級緩存中是否存在對應數據
- 一級緩存中有就直接返回
- 一級緩存中沒有就查詢數據庫
- 查出來的數據先存到一級緩存
- 然後sqlSession提交、回滾或者關閉,就將一級緩存中的數據存入二級緩存。