Hibernate 作為主流的 ORM 框架,其緩存機制是提升應用性能的關鍵。緩存分為一級緩存(Session 級)與二級緩存(SessionFactory 級),合理運用可大幅減少資料庫交互次數。
在理解緩存前,必須先掌握 Hibernate 實體對象的三種狀態:
Transaction tx = session.beginTransaction();
User user = new User();
user.setName("shun"); // Transient 狀態
session.save(user);
tx.commit(); // 轉為 Persistent 狀態
session.close(); // 轉為 Detached 狀態
Session session2 = sessionFactory.openSession();
tx = session2.beginTransaction();
user.setName("shun123");
session2.saveOrUpdate(user); // 重新變為 Persistent 狀態
tx.commit();
session2.close();
一級緩存是內建且強制啟用的,生命週期與 Session 綁定。同一 Session 內重複查詢相同 ID 的實體,會直接從緩存返回,不再發送 SQL。
Session session = sessionFactory.openSession();
User user1 = (User) session.load(User.class, 29L);
System.out.println(user1.getName());
User user2 = (User) session.load(User.class, 29L); // 不會發送第二條 SQL
System.out.println(user2.getName());
session.close();
輸出顯示只執行了一條 SELECT 語句。若手動調用 session.evict(user) 移除緩存,再次查詢則會重新查詢資料庫。
二級緩存跨 Session 共享,需顯式啟用並配置緩存提供者(如 EhCache)。
hibernate.cfg.xml 中啟用並指定提供者:
<property name="hibernate.cache.use_second_level_cache">true</property>
<property name="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</property>
ehcache.xml 配置文件:
<ehcache>
<diskStore path="java.io.tmpdir"/>
<defaultCache maxElementsInMemory="10000" eternal="false"
timeToIdleSeconds="120" timeToLiveSeconds="120" overflowToDisk="true" />
</ehcache>
<cache usage="read-write"/>查詢緩存需額外啟用 hibernate.cache.use_query_cache=true,並在查詢時調用 query.setCacheable(true)。適用於參數固定、頻繁執行的查詢。
// 第一個 Session
Session session = sessionFactory.openSession();
Query query = session.createQuery("from User where name = 'shun123'");
query.setCacheable(true);
Iterator iter = query.iterate();
while(iter.hasNext()) System.out.println(((User)iter.next()).getName());
session.close();
// 第二個 Session(共享 SessionFactory)
Session session2 = sessionFactory.openSession();
Query query2 = session2.createQuery("from User where name = 'shun123'");
query2.setCacheable(true);
Iterator iter2 = query2.iterate(); // 不再查詢資料庫
while(iter2.hasNext()) System.out.println(((User)iter2.next()).getName());
session2.close();
第二次查詢直接從二級緩存獲取 ID,避免了資料庫交互。
list() 方法不讀取查詢緩存(只寫),需使用 iterate() 或配合 setCacheable(true) 的 list()。setCacheable(true)。Serializable 接口才能被二級緩存。一級緩存自動生效,二級緩存需配置。合理使用可顯著降低資料庫負載,但需注意緩存一致性與過期策略,避免髒讀問題。