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();