Hibernate 性能之緩存與緩存算法
1. 前言
本節課和大家一起聊聊查詢緩存和緩存算法。
對于緩存的使用要有針對性,不能濫用緩存,因為緩存本身是需要占用系統資源的,緩存的維護也需要消耗系統性能。
所以,這個世界是平衡的!如何掌握平衡,多用心感悟!
通過本節課程的學習,你將了解到:
- 什么是查詢緩存,如何使用查詢緩存;
- 常用的緩存算法有哪些。
2. list()和 iterate()
在前面的課程里,咱們一起講解過 Query 對象,它提供了 list() 方法,此方法能接受 HQL 語句,查詢出開發者所需要的數據。
那么 list() 方法支持緩存嗎?也就是說 list() 方法查詢出來的數據會存儲到緩存中嗎?
本節課程中的緩存都是指二級緩存。
問題出來了,要找到答案很簡單,編寫一個實例,測試一下便知道結果 。創建 2 個 Session 對象,分別對同一個 HQL 語句進行查詢:
Session session = sessionFactory.openSession();
Transaction transaction = null;
try {
transaction = session.beginTransaction();
String hql = "from Student s";
Query query = session.createQuery(hql);
System.out.println("------------------第一次查詢-------------------");
List<Student> stus = query.list();
System.out.println(stus.size());
transaction.commit();
} catch (Exception e) {
transaction.rollback();
} finally {
session.close();
}
session = sessionFactory.openSession();
transaction = null;
try {
transaction = session.beginTransaction();
String hql = "from Student s";
Query query = session.createQuery(hql);
System.out.println("-----------------第二次查詢--------------------");
List<Student> stus = query.list();
System.out.println(stus.size());
transaction.commit();
} catch (Exception e) {
transaction.rollback();
} finally {
session.close();
}
查看控制臺上的輸出結果:
Hibernate:
select
student0_.stuId as stuId1_1_,
student0_.classRoomId as classRoo5_1_,
student0_.stuName as stuName2_1_,
student0_.stuPassword as stuPassw3_1_,
student0_.stuSex as stuSex4_1_
from
Student student0_
4
-----------------第二次查詢--------------------
Hibernate:
select
student0_.stuId as stuId1_1_,
student0_.classRoomId as classRoo5_1_,
student0_.stuName as stuName2_1_,
student0_.stuPassword as stuPassw3_1_,
student0_.stuSex as stuSex4_1_
from
Student student0_
4
從結果上可以看出,兩次查詢的 HQL 請求是相同的,但每一次都會重新發送 SQL 語句,是不是就得出結論,list() 方法與緩存無緣分呢?
結論可不要提出來的太早。
Query 還提供了一個方法 iterate(),從功能上做比較,和 list() 沒有多大區別,只是一個返回的是集合對象,一個返回的是迭代器對象,作用是一樣的。
但是不是就沒有其它的區別了?
不急,先了解一下 iterate() 方法的特點,用實例來說話:
Session session = sessionFactory.openSession();
Transaction transaction = null;
try {
transaction = session.beginTransaction();
String hql = "from Student s";
Query query = session.createQuery(hql);
System.out.println("------------------迭代查詢-------------------");
Iterator<Student> stus = query.iterate();
while(stus.hasNext()) {
Student stu= stus.next();
System.out.println("-------------------輸出結果------------------");
System.out.println("學生姓名:"+stu.getStuName());
}
transaction.commit();
} catch (Exception e) {
transaction.rollback();
} finally {
session.close();
}
截取運行后的一部分控制臺上的內容展示如下:
------------------迭代查詢-------------------
Hibernate:
select
student0_.stuId as col_0_0_
from
Student student0_
-------------------輸出結果------------------
Hibernate:
select
student0_.stuId as stuId1_1_0_,
student0_.classRoomId as classRoo5_1_0_,
student0_.stuName as stuName2_1_0_,
student0_.stuPassword as stuPassw3_1_0_,
student0_.stuSex as stuSex4_1_0_
from
Student student0_
where
student0_.stuId=?
學生姓名:Hibernate
當我們執行 iterate() 方法時,Hibernate 只是把所有的學生編號(主鍵)返回給應用程序。也就是說并沒有返回完整的學生信息。
它為什么要這么做了?
首先有一點是可以得出結論的,僅僅得到學生編號肯定比獲取全部學生信息是要快很多的。
當程序中需要學生其它數據的時候,這時 Hibernate 又會跑一次數據庫,根據前面獲取到的學生編號構建新的條件查詢,從數據庫中再次獲取數據。
天呀,真不閑累的慌。
為什么要這么做了?
這有點類似于延遲加載,很多時候,程序中并不急著使用數據,可能需要等某些依賴的邏輯成立后再使用。如此,iterate() 方法可快速獲取主鍵值,并安慰開發者,你看,我是有能力幫你獲取數據的。等需要更多時,我也是有能力拿到的。
Query 既提供 list() 方法,又提供 iterate() 方法不是沒有出發點的。這兩個方法很多時候結合起來使用,可以達到一種神奇的效果。
什么效果呢?
看一段實例:
Session session = sessionFactory.openSession();
Transaction transaction = null;
try {
transaction = session.beginTransaction();
String hql = "from Student s";
Query query = session.createQuery(hql);
System.out.println("------------------第一次使用 list()方法查詢-------------------");
List<Student> stus = query.list();
System.out.println(stus.size());
transaction.commit();
} catch (Exception e) {
transaction.rollback();
} finally {
session.close();
}
session = sessionFactory.openSession();
transaction = null;
try {
transaction = session.beginTransaction();
String hql = "from Student s";
Query query = session.createQuery(hql);
System.out.println("-----------------第二次使用iterate()方法查詢--------------------");
Iterator<Student> stus = query.iterate();
while (stus.hasNext()) {
Student stu = stus.next();
System.out.println("-------------------輸出結果------------------");
System.out.println("學生姓名:" + stu.getStuName());
}
transaction.commit();
} catch (Exception e) {
transaction.rollback();
} finally {
session.close();
}
兩者結合,交織中所碰觸出的火花,你 get 到了嗎?
先使用 list() 方法查詢出所有學生信息, hibernate 會把 list() 方法查詢出來的數據全部存儲到緩存中。但是,它自己不使用緩存中自己緩存的數據,它是勤勞的小蜜蜂,無私的奉獻。
誰會使用 list() 緩存的數據了?
輸出結果已經告訴了我們答案,iterate() 方法會使用 list() 方法緩存的數據。
對于一條查詢語句,Iterator 會先從數據庫中找到所有符合條件的記錄的主鍵 ID,再通過主鍵 ID 去緩存找,對于緩存中沒有的記錄,再構造語句從數據中查出,在緩存中沒有命中的話,效率很低。
那么,怎么聯合使用了?
建議在應用程序啟動或修改時使用 list,通過 list 緩存數據。需要更多數據時再使用 iterator。
好兄弟,一輩子,江湖上,有你也有我。
3. 查詢緩存
是不是 list() 方法真的就不能使用緩存,而只是作為 iterator() 身后的兄弟。
Hibernate 中提供的有查詢緩存的概念。查詢緩存只對 query.list() 方法起作用。查詢緩存依賴于二級緩存,因此一定要打開二級緩存。而且,在默認情況下,查詢緩存也是關閉的。
啟動查詢緩存
- 在 Hibernate 的主配置文件中添加如下配置信息:
<property name="cache.use_query_cache">true</property>
切記,使用查詢緩存是一定要加入下面的代碼:
query.setCacheable(true);
好吧,來一個實例,看看查詢緩存的威力。
Session session = sessionFactory.openSession();
Transaction transaction = null;
try {
transaction = session.beginTransaction();
String hql = "from Student s";
Query query = session.createQuery(hql);
query.setCacheable(true);
System.out.println("------------------第一次查詢-------------------");
List<Student> stus = query.list();
System.out.println(stus.size());
transaction.commit();
} catch (Exception e) {
transaction.rollback();
} finally {
session.close();
}
session = sessionFactory.openSession();
transaction = null;
try {
transaction = session.beginTransaction();
String hql = "from Student s";
Query query = session.createQuery(hql);
query.setCacheable(true);
System.out.println("-----------------第二次查詢--------------------");
List<Student> stus = query.list();
System.out.println(stus.size());
transaction.commit();
} catch (Exception e) {
transaction.rollback();
} finally {
session.close();
}
查看一下控制臺上的輸出結果:
------------------第一次查詢-------------------
Hibernate:
select
student0_.stuId as stuId1_1_,
student0_.classRoomId as classRoo5_1_,
student0_.stuName as stuName2_1_,
student0_.stuPassword as stuPassw3_1_,
student0_.stuSex as stuSex4_1_
from
Student student0_
4
-----------------第二次查詢--------------------
4
結論很明顯,第一次使用 list() 方法時,需要發送 SQL 語句,第二次時,就不再需要了,也就是說 list() 也是可以享受自己緩存的數據。但是必須啟動查詢緩存,且在代碼中明明確確指示出來。
4. 緩存算法
什么是緩存算法?
緩存是一個臨時存儲數據的地方,但是,這個地方可金貴的很,咱們可不能讓那些不經常使用的、過期的數據長時間待在里面。所以,必須有一種機制能隨時檢查一下緩存中的數據,哪些數據是可以繼續待在里面的,哪些數據需要移出去,給新來者挪出空間的,這就是所謂的緩存算法。
常用的緩存算法:
- LRU : Least Recently Used ,最近最少被使用的,每個緩存對象都記錄一個最后使用時間;
- LFU : Least Frequently Used ,最近使用頻率最少;
- FIFO: First in First Out ,這個簡單,定時清理時,先來的,先離開。
Session 和 SessionFactory 對象也提供的有與緩存管理有關的方法,方便開發者可以隨時按需清除緩存。如 evict() 等方法。
上一節課介紹 EHCache 緩存框架時,就要使用它的配置文件,其配置內容就是設置如何管理緩存。
5. 小結
好了!又到了說再見的時候了,本節課繼續上一節的內容,向大家介紹了查詢緩存,主要介紹了 Query 對象的 list 和 iterate 兩個方法,它們有各自的特點,也有各自調用的時機點。
聯合使用兩者,能更充分的發揮緩存的功效。
后面也給大家介紹了緩存算法,大家需要把此內容當成常識性知識。