MyBatis -- 整合Redis二級快取
一。MyBatis一級二級快取
一級快取:
MyBatis一級快取為SqlSession級別的快取,預設開啟,相同的SqlSession物件查詢相同條件的結果時,如果存在一級快取,那麼只會訪問資料庫一次,一級快取在sqlSession關閉後失效,呼叫cleanCache後會被清除,執行過增刪改後快取也會被清除。注意:一級快取不能跨session
以一個根據Student Id查詢Student為例:
@Test public void query1() throws IOException{ SqlSession session = getSession(); // 獲取對映介面例項 StudentMapper mapper = session.getMapper(StudentMapper.class); Student stu1_1 = mapper.queryStudentById("1"); Student stu1_2 = mapper.queryStudentById("1"); System.out.println(stu1_1 == stu1_2); }
輸出結果:
還可以看出SQL語句只執行了一次,這就是MyBatis的一級快取。記住這個概念:
同一個SqlSession查詢相同條件的結果時,存在一級快取只會查詢一次。第一次查詢
獲取到資料後會通過session設定到一級快取中,第二次查詢時通過session一級快取
判斷是否存在相同主鍵的資料,存在則直接返回引用,否則查詢資料庫。
二級快取:
二級快取為SqlSessionFactory級別的快取,預設不開啟,要開啟的話走下面這個步驟:
1.首先要在核心配置檔案中配置,這裡要注意順序,要寫在properties聲明後:
MyBatis官方文件上的說明:
也就是說預設是開啟的,直接用就OK了。不過這個開關只能控制二級快取,一級快取是不受影響的。
2.到需要使用二級快取的namespace中定義:
屬性詳解:
eviction:回收策略,預設使用LRU演算法。
也可以自己改,這裡就改成了FIFO(佇列),會把舊的資料清除掉,最先加入的資料最先
清除,不過這麼做是不行的,如果最先加入的資料是熱點資料,訪問量大,清除掉了又要
重新去資料庫中拉取,所以又有了LRU演算法。
官方文件上的解釋:
可用的收回策略有:
- LRU – 最近最少使用的:移除最長時間不被使用的物件。
- FIFO – 先進先出:按物件進入快取的順序來移除它們。
- SOFT – 軟引用:移除基於垃圾回收器狀態和軟引用規則的物件。
- WEAK – 弱引用:更積極地移除基於垃圾收集器狀態和弱
引用規則的物件。
預設的是 LRU。
flushInterval(重新整理間隔)可以被設定為任意的正整數,而且它們代表一個合理的毫秒 形式的時間段。預設情況是不設定,也就是沒有重新整理間隔,快取僅僅呼叫語句時重新整理。
size(引用數目)可以被設定為任意正整數,要記住你快取的物件數目和你執行環境的 可用記憶體資源數目。預設值是 1024。
readOnly(只讀)屬性可以被設定為 true 或 false。只讀的快取會給所有呼叫者返回緩 存物件的相同例項。因此這些物件不能被修改。這提供了很重要的效能優勢。可讀寫的快取 會返回快取物件的拷貝(通過序列化) 。這會慢一些,但是安全,因此預設是 false。
測試方法:
@Test
public void cacheLevel2() throws IOException {
// 獲取工廠
SqlSessionFactory factory = getFactory();
// 從同一個工廠中獲取session
SqlSession session1 = factory.openSession();
SqlSession session2 = factory.openSession();
StudentMapper mapper1 = session1.getMapper(StudentMapper.class);
StudentMapper mapper2 = session2.getMapper(StudentMapper.class);
Student stu1 = mapper1.queryStudentById("1");
session1.close();
Student stu2 = mapper2.queryStudentById("1");
System.out.println(stu1 == stu2);
}
注意:如果不把session關掉,查詢結果是不會設定到快取中的,一個快取一次只允許一個會話操作。因為考慮到如果兩個會話同時操作一份快取,會引起執行緒併發修改的問題,所以要關掉最先查詢的那個session,查詢結果才會被寫到二級快取中。
執行結果:
二。整合Redis
上面提到的FIFO LRU之類的快取策略預設都有實現類來支援他們,這些策略都是實現自
Cache介面的,我們要通過MyBatis整合Redis實現自己的快取策略,也是自定義一個實現類
來實現。不多BB,先上程式碼。
準備一個JavaBean:
實現Serializable的原因是我打算將Bean序列化成位元組陣列快取到Redis中,取出來之後再反序列化成實體類 。既然要序列化和反序列化,還需要準備兩個方法:
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class SerializableTools {
/**
* 反序列化
*
* @param bt
* @return
* @throws IOException
* @throws Exception
*/
public static Object byteArrayToObj(byte[] bt) throws Exception {
ByteArrayInputStream bais = new ByteArrayInputStream(bt);
ObjectInputStream ois = new ObjectInputStream(bais);
return ois.readObject();
}
/**
* 物件序列化
*
* @param obj
* @return
* @throws IOException
*/
public static byte[] ObjToByteArray(Object obj) throws IOException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(obj);
return bos.toByteArray();
}
}
OK,現在再定義個快取策略實現類,實現Cache介面,並重寫該類的一系列方法:
import java.io.IOException;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.apache.ibatis.cache.Cache;
import redis.clients.jedis.Jedis;
public class RedisCache implements Cache {
// 初始化Jedis
private Jedis jedis = new Jedis("127.0.0.1", 6379);
/*
* MyBatis會把對映檔案的名稱空間作為
* 唯一標識cacheId,標識這個快取策略屬於哪個namespace
* 這裡定義好,並提供一個構造器,初始化這個cacheId即可
*/
private String cacheId;
public RedisCache (String cacheId){
this.cacheId = cacheId;
}
/**
* 清空快取
*/
@Override
public void clear() {
// 但這方法不建議實現
}
@Override
public String getId() {
return cacheId;
}
/**
* MyBatis會自動呼叫這個方法檢測快取
* 中是否存在該物件。既然是自己實現的快取
* ,那麼當然是到Redis中找了。
*/
@Override
public Object getObject(Object arg0) {
// arg0 在這裡是鍵
try {
byte [] bt = jedis.get(SerializableTools.ObjToByteArray(arg0));
if (bt == null) { // 如果沒有這個物件,直接返回null
return null;
}
return SerializableTools.byteArrayToObj(bt);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
@Override
public ReadWriteLock getReadWriteLock() {
return new ReentrantReadWriteLock();
}
@Override
public int getSize() {
return Integer.parseInt(Long.toString(jedis.dbSize()));
}
/**
* MyBatis在讀取資料時,會自動呼叫此方法
* 將資料設定到快取中。這裡就寫入Redis
*/
@Override
public void putObject(Object arg0, Object arg1) {
/*
* arg0是key , arg1是值
* MyBatis會把查詢條件當做鍵,查詢結果當做值。
*/
try {
jedis.set(SerializableTools.ObjToByteArray(arg0), SerializableTools.ObjToByteArray(arg1));
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* MyBatis快取策略會自動檢測記憶體的大小,由此
* 決定是否刪除快取中的某些資料
*/
@Override
public Object removeObject(Object arg0) {
Object object = getObject(arg0);
try {
jedis.del(SerializableTools.ObjToByteArray(arg0));
} catch (IOException e) {
e.printStackTrace();
}
return object;
}
}
最後在配置檔案中寫上快取實現類的全類名
以及在需要使用該快取類的SQL語句上宣告:
至此,自實現的二級快取就可以用了。