數據庫每一次的查詢都是一次不小的開銷,例如連結的開啟、執行查詢指令,當數據庫與應用服務器不在同一個服務器上時,還必須有遠程調用、Socket的建立等開銷,在Hibernate這樣的ORM框架中,還有數據的封裝等開銷必須考慮進去。快取(Cache)是數據庫在內存中的臨時容器,從數據庫中讀取的數據在快取中會有一份臨時拷貝,當您查詢某個數據時,會先在快取中尋找是否有相對應的 拷貝,如果有的話就直接返回數據,而無需連接數據庫進行查詢,只有在快取中找不到數據時,才從數據庫中查詢數據,藉由快取,可以提升應用程序讀取數據時的 效能。對於Hibernate這樣的ORM框架來說,快取的機制更形重要,在Hibernate中快取分作兩個層級:Session level與SessionFactory level(又稱Second level快取)。這邊先介紹Session level的快取,在Hibernate中Session level快取會在使用主鍵加載數據或是延遲初始(Lazy Initialization) 時作用,Session level的快取隨著Session建立時建立,而Session銷毀時銷毀。Session會維護一個Map容器,並保留與目前Session發生關系的資料,當您透過主鍵來加載數據時,Session會先依據所要加載的類別與所 給定的主鍵,看看Map中是否已有數據,如果有的話就返回,若沒有就對數據庫進行查詢,並在加載數據後在Map中維護。可以透過==來比較兩個名稱是否參考至同一個對象,以檢驗這個事實:
Session session = sessionFactory.openSession();
User user1 = (User) session.load(User.class, new Integer(1));
User user2 = (User) session.load(User.class, new Integer(1));
System.out.println(user1 == user2); session.close();
第二次查詢數據時,由於在快取中找到數據對象,於是直接返回,這與第一次查詢到的數據對象是同一個實例,所以會顯示true的結果。可以透過evict()將某個對象從快取中移去,例如:
Session session = sessionFactory.openSession();
User user1 = (User) session.load(User.class, new Integer(1));
session.evict(user1);
User user2 = (User) session.load(User.class, new Integer(1));
System.out.println(user1 == user2);
session.close();
由於user1所參考的對象被從快取中移去了,在下一次查詢時,Session在Map容器中找不到對應的數據,於是重新查詢數據庫並再封裝一個對象,所以user1與user2參考的是不同的對象,結果會顯示false。也可以使用clear()清除快取中的所有對象,例如:
Session session = sessionFactory.openSession();
User user1 = (User) session.load(User.class, new Integer(1));
session.clear();
User user2 = (User) session.load(User.class, new Integer(1));
System.out.println(user1 == user2);
session.close();
同樣的道理,這次也會顯示false。Session level的快取隨著Session建立與銷毀,看看下面這個程序片段:
Session session1 = sessionFactory.openSession();
User user1 = (User) session1.load(User.class, new new Integer(1));
session1.close();
Session session2 =sessionFactory.openSession();
User user2 = (User)session2.load(User.class, new Integer(1));
session2.close();
System.out.println(user1 == user2);
第一個Session在關閉後,快取也關閉了,在第二個Session的查詢中並無法用到第一個Session的快取,兩個Session階段所查詢到的並不是同一個對象,結果會顯示false。在加載大量數據時,Session level 快取的內容會太多,記得要自行執行clear()清除快取或是用evict()移去不使用對象,以釋放快取所占據的資源。 Session在使用save()儲存對象時,會將要儲存的對象納入Session level快取管理,在進行大量數據儲存時,快取中的實例大量增加,最後會導致OutOfMemoryError,可以每隔一段時間使用Session的 flush()強制儲存對象,並使用clear()清除快取,例如:
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
while(....) { // 大量加載對象時的循環示意
....
session.save(someObject);
if(count % 100 == 0) { // 每100筆資料
session.flush(); // 送入數據庫
session.clear(); // 清除快取
}
}
tx.commit();
session.close();
在SQL Server、Oracle等數據庫中,可以在Hibernate設定文件中設定屬性hibernate.jdbc.batch_size來控制每多少筆數據就送至數據庫,例如:
....
<hibernate-configuration>
<session-factory>
....
<property name="hibernate.jdbc.batch_size">100</property>
....
</session-factory>
<hibernate-configuration>
在MySQL中則不支持這個功能。