資料庫每一次的查詢都是一次不小的開銷,例如連結的開啟、執行查詢指令,當資料庫與應用伺服器不在同一個伺服器上時,還必須有遠程調用、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();
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();
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();
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);
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的setReadOnly(object, true),設定某物件為唯讀,對於唯讀資料,不會在快取中維護一個複本,不會執行dirty check。