Java三大框架之——Hibernate中的三種資料持久狀態和快取機制
Hibernate中的三種狀態
瞬時狀態:剛建立的物件還沒有被Session持久化、快取中不存在這個物件的資料並且資料庫中沒有這個物件對應的資料為瞬時狀態這個時候是沒有OID。
持久狀態:物件經過Session持久化操作,快取中存在這個物件的資料為持久狀態並且資料庫中存在這個物件對應的資料為持久狀態這個時候有OID。
遊離狀態:當Session關閉,快取中不存在這個物件資料而資料庫中有這個物件的資料並且有OID為遊離狀態。
注:OID為了在系統中能夠找到所需物件,我們需要為每一個物件分配一個唯一的表示號。在關係資料庫中我們稱之為關鍵字,而在物件術語中,則叫做物件標識
(Object identifier-OID).通常OID在內部都使用一個或多個大整數表示,而在應用程式中則提供一個完整的類為其他類提供獲取、操作。
Hibernate資料狀態圖:
需要注意的是:
當物件的臨時狀態將變為持久化狀態。當物件在持久化狀態時,它一直位於 Session 的快取中,對它的任何操作在事務提交時都將同步到資料庫,因此,對一個已經持久的物件呼叫 save() 或 update() 方法是沒有意義的。
Student stu = new Strudnet(); stu.setCarId(“200234567”); stu.setId(“100”); // 開啟 Session, 開啟事務 //將stu物件持久化操作 session.save(stu); stu.setCardId(“20076548”); //再次對stu物件進行持久化操作 session.save(stu); // 無效 session.update(stu); // 無效 // 提交事務,關閉 Session
Hibernate快取機制
什麼是快取?
快取是介於應用程式和物理資料來源之間,其作用是為了降低應用程式對物理資料來源訪問的頻次,從而提高了應用的執行效能。快取內的資料是對物理資料來源中的資料的複製,應用程式在執行時從快取讀寫資料,在特定的時刻或事件會同步快取和物理資料來源的資料。
快取有什麼好處?
快取的好處是降低了資料庫的訪問次數,提高應用效能,減少了讀寫資料的時間
什麼時候適合用快取?
程式中經常用到一些不變的資料內容,從資料庫查出來以後不會去經常修改它而又經常要用到的就可以考慮做一個快取,以後讀取就從快取來讀取,而不必每次都去查詢資料庫。因為硬碟的速度比記憶體的速度慢的多。從而提高了程式的效能,快取的出現就會為了解決這個問題
Hibernate中的快取
Hibernate中的快取包括一級快取(Session快取)、二級快取(SessionFactory快取)和查詢快取。
一級快取(Session快取)
由於Session物件的生命週期通常對應一個數據庫事務或者一個應用事務,因此它的快取是事務範圍的快取。Session級快取是必需的,不允許而且事實上也無法卸除。在Session級快取中,持久化類的每個例項都具有唯一的OID。
當應用程式呼叫Session的save()、update()、savaeOrUpdate()、get()或load(),以及呼叫查詢介面的list()、iterate()或filter()方法時,如果在Session快取中還不存在相應的物件,Hibernate就會把該物件加入到第一級快取中。
當清理快取時,Hibernate會根據快取中物件的狀態變化來同步更新資料庫。
Session為應用程式提供了兩個管理快取的方法:evict(Object obj):從快取中清除引數指定的持久化物件。clear():清空快取中所有持久化物件。
public static void testOneLeveCache(){ Session session=HibernateUtil.getSession(); //獲取持久化物件Dept Dept d1=(Dept)session.get(Dept.class, 10); //再次獲取持久化物件Dept Dept d2=(Dept)session.get(Dept.class, 10); session.close(); }
通過Session的get()方法獲取到了Dept物件預設會將Dept物件儲存到一級快取中(Session快取) 當第二次獲取的時候會先從一級快取中查詢對應的物件(前提是不能清空或關閉Session否則一級快取會清空或銷燬)如果一級快取中存在相應的物件就不會到資料庫中查詢 所以只執行一次查詢的查詢程式碼如下:
Hibernate:
select
dept0_.DEPTNO as DEPTNO0_0_,
dept0_.DNAME as DNAME0_0_,
dept0_.LOC as LOC0_0_
from
SCOTT.DEPT dept0_
where
dept0_.DEPTNO=?
如何清除一級快取?
通過session.clear();//清除所有快取
session.evict();//清除指定快取
public static void testOneLeveCache(){ Session session=HibernateUtil.getSession(); SessionFactory sf = HibernateUtil.getSessionFactory(); //獲取持久化物件Dept Dept d1=(Dept)session.get(Dept.class, 10); session.clear();//清除所有快取 session.evict(d1);//清除指定快取 //再次獲取持久化物件Dept Dept d2=(Dept)session.get(Dept.class, 10); session.close(); }
使用session.evict(Object obj)會刪除指定的Bean所以當你查詢被你刪除二級快取的Bean時也會執行兩條SQL語句
使用Session.clear()清除後會發現執行了兩條SQL語句:
Hibernate: select dept0_.DEPTNO as DEPTNO0_0_, dept0_.DNAME as DNAME0_0_, dept0_.LOC as LOC0_0_ from SCOTT.DEPT dept0_ where dept0_.DEPTNO=? Hibernate: select dept0_.DEPTNO as DEPTNO0_0_, dept0_.DNAME as DNAME0_0_, dept0_.LOC as LOC0_0_ from SCOTT.DEPT dept0_ where dept0_.DEPTNO=?
二級快取(SessionFactory快取)
由於SessionFactory物件的生命週期和應用程式的整個過程對應,因此Hibernate二級快取是程序範圍或者叢集範圍的快取,有可能出現併發問題,因此需要採用適當的併發訪問策略,該策略為被快取的資料提供了事務隔離級別。
save、update、saveOrupdate、load、get、list、query、Criteria方法都會填充二級快取
get、load、iterate會從二級快取中取資料session.save(user)
如果user主鍵使用“native”生成,則不放入二級快取.
第二級快取是可選的,是一個可配置的外掛,預設下SessionFactory不會啟用這個外掛。Hibernate提供了org.hibernate.cache.CacheProvider介面,它充當快取外掛與Hibernate之間的介面卡。
Hibernate的二級快取策略的一般過程如下:
1) 條件查詢的時候,總是發出一條select * from table_name where …. (選擇所有欄位)這樣的SQL語句查詢資料庫,一次獲得所有的資料物件。
2) 把獲得的所有資料物件根據ID放入到第二級快取中。
3) 當Hibernate根據ID訪問資料物件的時候,首先從Session一級快取中查;查不到,如果配置了二級快取,那麼從二級快取中查;查不到,再查詢資料庫,把結果按照ID放入到快取。
4) 刪除、更新、增加資料的時候,同時更新快取。
Hibernate的二級快取策略,是針對於ID查詢的快取策略,對於條件查詢則毫無作用。為此,Hibernate提供了針對條件查詢的查詢快取(Query Cache)。
配置二級快取(SessionFactory快取):
在hibernate.cfg.xml中配置以下程式碼
<!-- 開啟二級快取 -->
<property name="hibernate.cache.use_second_level_cache">true</property>
<!-- 為hibernate指定二級快取的實現類 -->
<property name="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</property>
指明哪些類需要放入二級快取,需要長期使用到的物件才有必要放入二級快取放入二級快取的方式有兩種:
1.在hibernate.cfg.xml中配置
<class-cache class="entity.PetInfo" usage="read-only" /> //不允許更新快取中的物件 <class-cache class="entity.PetInfo" usage="read-write" /> //允許更新快取中的物件
2.在Bean.hbm檔案中配置
<hibernate-mapping> <class name="com.bdqn.entity.Dept" table="DEPT" schema="SCOTT" > <cache usage="read-only"/>//將這個類放入二級快取 <id name="deptno" type="java.lang.Integer"> <column name="DEPTNO" precision="2" scale="0" /> <generator class="assigned"></generator> </id> <property name="dname" type="java.lang.String"> <column name="DNAME" length="14" /> </property> </class> </hibernate-mapping>
在ehcache.xml配置檔案中可以設定快取的最大數量、是否永久有效、時間等
<defaultCache maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="120" timeToLiveSeconds="120" overflowToDisk="true" />
如何清除二級快取?
需要用SessionFactory來管理二級快取程式碼如下:
sessionFactory.evict(Entity.class);//清除所有Entity sessionFactory.evict(Entity.class,id);//清除指定Entity 比如: //用SessionFacotry管理二級快取 SessionFactory factory=HibernateUtils.getSessionFactory(); //evict()把id為1的Student物件從二級快取中清除. factory.evict(Student.class, 1); //evict()清除所有二級快取. factory.evict(Student.class);
什麼樣的資料適合存放到第二級快取中?
1) 很少被修改的資料
2) 不是很重要的資料,允許出現偶爾併發的資料
3) 不會被併發訪問的資料
4) 常量資料
不適合存放到第二級快取的資料?
1) 經常被修改的資料
2) 絕對不允許出現併發訪問的資料,如財務資料,絕對不允許出現併發
3) 與其他應用共享的資料。
查詢快取(Query Cache)
hibernate的查詢快取是主要是針對普通屬性結果集的快取, 而對於實體物件的結果集只快取id。
在一級快取,二級快取和查詢快取都開啟的情況下作查詢操作時這樣的:
查詢普通屬性,會先到查詢快取中取,如果沒有,則查詢資料庫;查詢實體,會先到查詢快取中取id,如果有,則根據id到快取(一級/二級)中取實體,如果快取中取不到實體,再查詢資料庫。
在hibernate.cfg.xml配置檔案中,開啟查詢快取
<!-- 是否開啟查詢快取,true開啟查詢快取,false關閉查詢快取 -->
<property name="cache.use_query_cache">true</property>
開啟查詢快取後還需要在程式中進行啟用查詢快取
public static void testQueryCache(){ Session session=HibernateUtil.getSession(); String hql="from Emp as e"; Query query=session.createQuery(hql); query.setCacheable(true);//啟用查詢快取(二級快取) List<Emp> empList=query.list(); session.close(); }
查詢快取是基於二級快取機制如果根據Bean的屬性查詢可以不開啟二級快取程式碼如下:
session = HibernateUtils.getSession(); t = session.beginTransaction(); Query query = session.createQuery("select s.name from Student s"); //啟用查詢快取 query.setCacheable(true); List<String> names = query.list(); for (Iterator<String> it = names.iterator(); it.hasNext();) { String name = it.next(); System.out.println(name); } System.out.println("================================"); query = session.createQuery("select s.name from Student s"); //啟用查詢快取 query.setCacheable(true); //沒有發出查詢語句,因為這裡使用的查詢快取 names = query.list(); for (Iterator<String> it = names.iterator(); it.hasNext();) { String name = it.next(); System.out.println(name); } t.commit();