抓取策略(fetching strategy) 是指:當應用程序需要在(Hibernate實(shí)體對象圖的)關(guān)聯(lián)關(guān)系間進(jìn)行導航的時(shí)候, Hibernate如何獲取關(guān)聯(lián)對象的策略。抓取策略可以在O/R映射的元數據中聲明,也可以在特定的HQL 或條件查詢(xún)(Criteria Query)中重載聲明。
Hibernate3 定義了如下幾種抓取策略:
連接抓?。↗oin fetching) - Hibernate通過(guò) 在SELECT語(yǔ)句使用OUTER JOIN(外連接)來(lái) 獲得對象的關(guān)聯(lián)實(shí)例或者關(guān)聯(lián)集合。
查詢(xún)抓?。⊿elect fetching) - 另外發(fā)送一條 SELECT 語(yǔ)句抓取當前對象的關(guān)聯(lián)實(shí)體或集合。除非你顯式的指定lazy="false"禁止 延遲抓?。╨azy fetching),否則只有當你真正訪(fǎng)問(wèn)關(guān)聯(lián)關(guān)系的時(shí)候,才會(huì )執行第二條select語(yǔ)句。
子查詢(xún)抓?。⊿ubselect fetching) - 另外發(fā)送一條SELECT 語(yǔ)句抓取在前面查詢(xún)到(或者抓取到)的所有實(shí)體對象的關(guān)聯(lián)集合。除非你顯式的指定lazy="false" 禁止延遲抓?。╨azy fetching),否則只有當你真正訪(fǎng)問(wèn)關(guān)聯(lián)關(guān)系的時(shí)候,才會(huì )執行第二條select語(yǔ)句。
批量抓?。˙atch fetching) - 對查詢(xún)抓取的優(yōu)化方案, 通過(guò)指定一個(gè)主鍵或外鍵列表,Hibernate使用單條SELECT語(yǔ)句獲取一批對象實(shí)例或集合。
Hibernate會(huì )區分下列各種情況:
Immediate fetching,立即抓取 - 當宿主被加載時(shí),關(guān)聯(lián)、集合或屬性被立即抓取。
Lazy collection fetching,延遲集合抓取- 直到應用程序對集合進(jìn)行了一次操作時(shí),集合才被抓取。(對集合而言這是默認行為。)
Proxy fetching,代理抓取 - 對返回單值的關(guān)聯(lián)而言,當其某個(gè)方法被調用,而非對其關(guān)鍵字進(jìn)行g(shù)et操作時(shí)才抓取。
Lazy attribute fetching,屬性延遲加載 - 對屬性或返回單值的關(guān)聯(lián)而言,當其實(shí)例變量被訪(fǎng)問(wèn)的時(shí)候進(jìn)行抓?。ㄐ枰\行時(shí)字節碼強化)。這一方法很少是必要的。
這里有兩個(gè)正交的概念:關(guān)聯(lián)何時(shí)被抓取,以及被如何抓?。〞?huì )采用什么樣的SQL語(yǔ)句)。不要混淆它們!我們使用抓取來(lái)改善性能。我們使用延遲來(lái)定義一些契約,對某特定類(lèi)的某個(gè)脫管的實(shí)例,知道有哪些數據是可以使用的。
默認情況下,Hibernate 3對集合使用延遲select抓取,對返回單值的關(guān)聯(lián)使用延遲代理抓取。對幾乎是所有的應用而言,其絕大多數的關(guān)聯(lián),這種策略都是有效的。
注意:假若你設置了hibernate.default_batch_fetch_size,Hibernate會(huì )對延遲加載采取批量抓取優(yōu)化措施(這種優(yōu)化也可能會(huì )在更細化的級別打開(kāi))。
然而,你必須了解延遲抓取帶來(lái)的一個(gè)問(wèn)題。在一個(gè)打開(kāi)的Hibernate session上下文之外調用延遲集合會(huì )導致一次意外。比如:
s = sessions.openSession();Transaction tx = s.beginTransaction();User u = (User) s.createQuery("from User u where u.name=:userName").setString("userName", userName).uniqueResult();Map permissions = u.getPermissions();tx.commit();s.close();Integer accessLevel = (Integer) permissions.get("accounts"); // Error!在Session關(guān)閉后,permessions集合將是未實(shí)例化的、不再可用,因此無(wú)法正常載入其狀態(tài)。 Hibernate對脫管對象不支持延遲實(shí)例化. 這里的修改方法是:將permissions讀取數據的代碼 移到tx.commit()之前。
除此之外,通過(guò)對關(guān)聯(lián)映射指定lazy="false",我們也可以使用非延遲的集合或關(guān)聯(lián)。但是, 對絕大部分集合來(lái)說(shuō),更推薦使用延遲方式抓取數據。如果在你的對象模型中定義了太多的非延遲關(guān)聯(lián),Hibernate最終幾乎需要在每個(gè)事務(wù)中載入整個(gè)數據庫到內存中!
但是,另一方面,在一些特殊的事務(wù)中,我們也經(jīng)常需要使用到連接抓?。ㄋ旧砩暇褪欠茄舆t的),以代替查詢(xún)抓取。 下面我們將會(huì )很快明白如何具體的定制Hibernate中的抓取策略。在Hibernate3中,具體選擇哪種抓取策略的機制是和選擇 單值關(guān)聯(lián)或集合關(guān)聯(lián)相一致的。
查詢(xún)抓?。J的)在N+1查詢(xún)的情況下是極其脆弱的,因此我們可能會(huì )要求在映射文檔中定義使用連接抓?。?
<set name="permissions"fetch="join"><key column="userId"/><one-to-many class="Permission"/></set
<many-to-one name="mother" class="Cat" fetch="join"/>
在映射文檔中定義的抓取策略將會(huì )有產(chǎn)生以下影響:
通過(guò)get()或load()方法取得數據。
只有在關(guān)聯(lián)之間進(jìn)行導航時(shí),才會(huì )隱式的取得數據(延遲抓取)。
條件查詢(xún)
通常情況下,我們并不使用映射文檔進(jìn)行抓取策略的定制。更多的是,保持其默認值,然后在特定的事務(wù)中, 使用HQL的左連接抓?。╨eft join fetch) 對其進(jìn)行重載。這將通知 Hibernate在第一次查詢(xún)中使用外部關(guān)聯(lián)(outer join),直接得到其關(guān)聯(lián)數據。 在條件查詢(xún) API中,應該調用 setFetchMode(FetchMode.JOIN)語(yǔ)句。
也許你喜歡僅僅通過(guò)條件查詢(xún),就可以改變get() 或 load()語(yǔ)句中的數據抓取策略。例如:
User user = (User) session.createCriteria(User.class).setFetchMode("permissions", FetchMode.JOIN).add( Restrictions.idEq(userId) ).uniqueResult();(這就是其他ORM解決方案的“抓取計劃(fetch plan)”在Hibernate中的等價(jià)物。)
截然不同的一種避免N+1次查詢(xún)的方法是,使用二級緩存。
在Hinerbate中,對集合的延遲抓取的采用了自己的實(shí)現方法。但是,對于單端關(guān)聯(lián)的延遲抓取,則需要采用 其他不同的機制。單端關(guān)聯(lián)的目標實(shí)體必須使用代理,Hihernate在運行期二進(jìn)制級(通過(guò)優(yōu)異的CGLIB庫), 為持久對象實(shí)現了延遲載入代理。
默認的,Hibernate3將會(huì )為所有的持久對象產(chǎn)生代理(在啟動(dòng)階段),然后使用他們實(shí)現 多對一(many-to-one)關(guān)聯(lián)和一對一(one-to-one) 關(guān)聯(lián)的延遲抓取。
在映射文件中,可以通過(guò)設置proxy屬性為目標class聲明一個(gè)接口供代理接口使用。 默認的,Hibernate將會(huì )使用該類(lèi)的一個(gè)子類(lèi)。 注意:被代理的類(lèi)必須實(shí)現一個(gè)至少包可見(jiàn)的默認構造函數,我們建議所有的持久類(lèi)都應擁有這樣的構造函數
在如此方式定義一個(gè)多態(tài)類(lèi)的時(shí)候,有許多值得注意的常見(jiàn)性的問(wèn)題,例如:
<class name="Cat" proxy="Cat">......<subclass name="DomesticCat">.....</subclass></class>
首先,Cat實(shí)例永遠不可以被強制轉換為DomesticCat, 即使它本身就是DomesticCat實(shí)例。
Cat cat = (Cat) session.load(Cat.class, id); // instantiate a proxy (does not hit the db)if ( cat.isDomesticCat() ) { // hit the db to initialize the proxyDomesticCat dc = (DomesticCat) cat; // Error!....}其次,代理的“==”可能不再成立。
Cat cat = (Cat) session.load(Cat.class, id); // instantiate a Cat proxyDomesticCat dc =(DomesticCat) session.load(DomesticCat.class, id); // acquire new DomesticCat proxy!System.out.println(cat==dc); // false
雖然如此,但實(shí)際情況并沒(méi)有看上去那么糟糕。雖然我們現在有兩個(gè)不同的引用,分別指向這兩個(gè)不同的代理對象, 但實(shí)際上,其底層應該是同一個(gè)實(shí)例對象:
cat.setWeight(11.0); // hit the db to initialize the proxySystem.out.println( dc.getWeight() ); // 11.0
第三,你不能對“final類(lèi)”或“具有final方法的類(lèi)”使用CGLIB代理。
最后,如果你的持久化對象在實(shí)例化時(shí)需要某些資源(例如,在實(shí)例化方法、默認構造方法中), 那么代理對象也同樣需要使用這些資源。實(shí)際上,代理類(lèi)是持久化類(lèi)的子類(lèi)。
這些問(wèn)題都源于Java的單根繼承模型的天生限制。如果你希望避免這些問(wèn)題,那么你的每個(gè)持久化類(lèi)必須實(shí)現一個(gè)接口, 在此接口中已經(jīng)聲明了其業(yè)務(wù)方法。然后,你需要在映射文檔中再指定這些接口。例如:
<class name="CatImpl" proxy="Cat">......<subclass name="DomesticCatImpl" proxy="DomesticCat">.....</subclass></class>
這里CatImpl實(shí)現了Cat接口, DomesticCatImpl實(shí)現DomesticCat接口。 在load()、iterate()方法中就會(huì )返回 Cat和DomesticCat的代理對象。 (注意list()并不會(huì )返回代理對象。)
Cat cat = (Cat) session.load(CatImpl.class, catid);Iterator iter = session.iterate("from CatImpl as cat where cat.name=‘fritz‘");Cat fritz = (Cat) iter.next();這里,對象之間的關(guān)系也將被延遲載入。這就意味著(zhù),你應該將屬性聲明為Cat,而不是CatImpl。
但是,在有些方法中是不需要使用代理的。例如:
equals()方法,如果持久類(lèi)沒(méi)有重載equals()方法。
hashCode()方法,如果持久類(lèi)沒(méi)有重載hashCode()方法。
標志符的getter方法。
Hibernate將會(huì )識別出那些重載了equals()、或hashCode()方法的持久化類(lèi)。
在Session范圍之外訪(fǎng)問(wèn)未初始化的集合或代理,Hibernate將會(huì )拋出LazyInitializationException異常。 也就是說(shuō),在分離狀態(tài)下,訪(fǎng)問(wèn)一個(gè)實(shí)體所擁有的集合,或者訪(fǎng)問(wèn)其指向代理的屬性時(shí),會(huì )引發(fā)此異常。
有時(shí)候我們需要保證某個(gè)代理或者集合在Session關(guān)閉前就已經(jīng)被初始化了。 當然,我們可以通過(guò)強行調用cat.getSex()或者cat.getKittens().size()之類(lèi)的方法來(lái)確保這一點(diǎn)。 但是這樣的程序會(huì )造成讀者的疑惑,也不符合通常的代碼規范。
靜態(tài)方法Hibernate.initialized() 為你的應用程序提供了一個(gè)便捷的途徑來(lái)延遲加載集合或代理。 只要它的Session處于open狀態(tài),Hibernate.initialize(cat) 將會(huì )為cat強制對代理實(shí)例化。 同樣,Hibernate.initialize( cat.getKittens() ) 對kittens的集合具有同樣的功能。
還有另外一種選擇,就是保持Session一直處于open狀態(tài),直到所有需要的集合或代理都被載入。 在某些應用架構中,特別是對于那些使用Hibernate進(jìn)行數據訪(fǎng)問(wèn)的代碼,以及那些在不同應用層和不同物理進(jìn)程中使用Hibernate的代碼。 在集合實(shí)例化時(shí),如何保證Session處于open狀態(tài)經(jīng)常會(huì )是一個(gè)問(wèn)題。有兩種方法可以解決此問(wèn)題:
在一個(gè)基于Web的應用中,可以利用servlet過(guò)濾器(filter),在用戶(hù)請求(request)結束、頁(yè)面生成 結束時(shí)關(guān)閉Session(這里使用了在展示層保持打開(kāi)Session模式(Open Session in View)), 當然,這將依賴(lài)于應用框架中異常需要被正確的處理。在返回界面給用戶(hù)之前,乃至在生成界面過(guò)程中發(fā)生異常的情況下, 正確關(guān)閉Session和結束事務(wù)將是非常重要的, Servlet過(guò)濾器必須如此訪(fǎng)問(wèn)Session,才能保證正確使用Session。 我們推薦使用ThreadLocal 變量保存當前的Session (可以參考第 1.4 節 “與Cat同樂(lè )”的例子實(shí)現)。
在一個(gè)擁有單獨業(yè)務(wù)層的應用中,業(yè)務(wù)層必須在返回之前,為web層“準備”好其所需的數據集合。這就意味著(zhù) 業(yè)務(wù)層應該載入所有表現層/web層所需的數據,并將這些已實(shí)例化完畢的數據返回。通常,應用程序應該 為web層所需的每個(gè)集合調用Hibernate.initialize()(這個(gè)調用必須發(fā)生咱session關(guān)閉之前); 或者使用帶有FETCH從句,或FetchMode.JOIN的Hibernate查詢(xún), 事先取得所有的數據集合。如果你在應用中使用了Command模式,代替Session Facade , 那么這項任務(wù)將會(huì )變得簡(jiǎn)單的多。
你也可以通過(guò)merge()或lock()方法,在訪(fǎng)問(wèn)未實(shí)例化的集合(或代理)之前, 為先前載入的對象綁定一個(gè)新的Session。 顯然,Hibernate將不會(huì ),也不應該自動(dòng)完成這些任務(wù),因為這將引入一個(gè)特殊的事務(wù)語(yǔ)義。
有時(shí)候,你并不需要完全實(shí)例化整個(gè)大的集合,僅需要了解它的部分信息(例如其大?。?、或者集合的部分內容。
你可以使用集合過(guò)濾器得到其集合的大小,而不必實(shí)例化整個(gè)集合:
( (Integer) s.createFilter( collection, "select count(*)" ).list().get(0) ).intValue()
這里的createFilter()方法也可以被用來(lái)有效的抓取集合的部分內容,而無(wú)需實(shí)例化整個(gè)集合:
s.createFilter( lazyCollection, "").setFirstResult(0).setMaxResults(10).list();
Hibernate可以充分有效的使用批量抓取,也就是說(shuō),如果僅一個(gè)訪(fǎng)問(wèn)代理(或集合),那么Hibernate將不載入其他未實(shí)例化的代理。 批量抓取是延遲查詢(xún)抓取的優(yōu)化方案,你可以在兩種批量抓取方案之間進(jìn)行選擇:在類(lèi)級別和集合級別。
類(lèi)/實(shí)體級別的批量抓取很容易理解。假設你在運行時(shí)將需要面對下面的問(wèn)題:你在一個(gè)Session中載入了25個(gè) Cat實(shí)例,每個(gè)Cat實(shí)例都擁有一個(gè)引用成員owner, 其指向Person,而Person類(lèi)是代理,同時(shí)lazy="true"。 如果你必須遍歷整個(gè)cats集合,對每個(gè)元素調用getOwner()方法,Hibernate將會(huì )默認的執行25次SELECT查詢(xún), 得到其owner的代理對象。這時(shí),你可以通過(guò)在映射文件的Person屬性,顯式聲明batch-size,改變其行為:
<class name="Person" batch-size="10">...</class>
隨之,Hibernate將只需要執行三次查詢(xún),分別為10、10、 5。
你也可以在集合級別定義批量抓取。例如,如果每個(gè)Person都擁有一個(gè)延遲載入的Cats集合, 現在,Sesssion中載入了10個(gè)person對象,遍歷person集合將會(huì )引起10次SELECT查詢(xún), 每次查詢(xún)都會(huì )調用getCats()方法。如果你在Person的映射定義部分,允許對cats批量抓取, 那么,Hibernate將可以預先抓取整個(gè)集合。請看例子:
<class name="Person"><set name="cats" batch-size="3">...</set></class>
如果整個(gè)的batch-size是3(筆誤?),那么Hibernate將會(huì )分四次執行SELECT查詢(xún), 按照3、3、3、1的大小分別載入數據。這里的每次載入的數據量還具體依賴(lài)于當前Session中未實(shí)例化集合的個(gè)數。
如果你的模型中有嵌套的樹(shù)狀結構,例如典型的帳單-原料結構(bill-of-materials pattern),集合的批量抓取是非常有用的。 (盡管在更多情況下對樹(shù)進(jìn)行讀取時(shí),嵌套集合(nested set)或原料路徑(materialized path)(××) 是更好的解決方法。)
假若一個(gè)延遲集合或單值代理需要抓取,Hibernate會(huì )使用一個(gè)subselect重新運行原來(lái)的查詢(xún),一次性讀入所有的實(shí)例。這和批量抓取的實(shí)現方法是一樣的,不會(huì )有破碎的加載。
Hibernate3對單獨的屬性支持延遲抓取,這項優(yōu)化技術(shù)也被稱(chēng)為組抓?。╢etch groups)。 請注意,該技術(shù)更多的屬于市場(chǎng)特性。在實(shí)際應用中,優(yōu)化行讀取比優(yōu)化列讀取更重要。但是,僅載入類(lèi)的部分屬性在某些特定情況下會(huì )有用,例如在原有表中擁有幾百列數據、數據模型無(wú)法改動(dòng)的情況下。
可以在映射文件中對特定的屬性設置lazy,定義該屬性為延遲載入。
<class name="Document"><id name="id"><generator class="native"/></id><property name="name" not-null="true" length="50"/><property name="summary" not-null="true" length="200" lazy="true"/><property name="text" not-null="true" length="2000" lazy="true"/></class>
屬性的延遲載入要求在其代碼構建時(shí)加入二進(jìn)制指示指令(bytecode instrumentation),如果你的持久類(lèi)代碼中未含有這些指令, Hibernate將會(huì )忽略這些屬性的延遲設置,仍然將其直接載入。
你可以在A(yíng)nt的Task中,進(jìn)行如下定義,對持久類(lèi)代碼加入“二進(jìn)制指令。”
<target name="instrument" depends="compile"><taskdef name="instrument" classname="org.hibernate.tool.instrument.InstrumentTask"><classpath path="${jar.path}"/><classpath path="${classes.dir}"/><classpath refid="lib.class.path"/></taskdef><instrument verbose="true"><fileset dir="${testclasses.dir}/org/hibernate/auction/model"><include name="*.class"/></fileset></instrument></target>還有一種可以?xún)?yōu)化的方法,它使用HQL或條件查詢(xún)的投影(projection)特性,可以避免讀取非必要的列, 這一點(diǎn)至少對只讀事務(wù)是非常有用的。它無(wú)需在代碼構建時(shí)“二進(jìn)制指令”處理,因此是一個(gè)更加值得選擇的解決方法。
有時(shí)你需要在HQL中通過(guò)抓取所有屬性,強行抓取所有內容。
Hibernate的Session在事務(wù)級別進(jìn)行持久化數據的緩存操作。 當然,也有可能分別為每個(gè)類(lèi)(或集合),配置集群、或JVM級別(SessionFactory級別)的緩存。 你甚至可以為之插入一個(gè)集群的緩存。注意,緩存永遠不知道其他應用程序對持久化倉庫(數據庫)可能進(jìn)行的修改 (即使可以將緩存數據設定為定期失效)。
默認情況下,Hibernate使用EHCache進(jìn)行JVM級別的緩存(目前,Hibernate已經(jīng)廢棄了對JCS的支持,未來(lái)版本中將會(huì )去掉它)。 你可以通過(guò)設置hibernate.cache.provider_class屬性,指定其他的緩存策略, 該緩存策略必須實(shí)現org.hibernate.cache.CacheProvider接口。
表 20.1. 緩存策略提供商(Cache Providers)
| Cache | Provider class | Type | Cluster Safe | Query Cache Supported |
|---|---|---|---|---|
| Hashtable (not intended for production use) | org.hibernate.cache.HashtableCacheProvider | memory | yes | |
| EHCache | org.hibernate.cache.EhCacheProvider | memory, disk | yes | |
| OSCache | org.hibernate.cache.OSCacheProvider | memory, disk | yes | |
| SwarmCache | org.hibernate.cache.SwarmCacheProvider | clustered (ip multicast) | yes (clustered invalidation) | |
| JBoss TreeCache | org.hibernate.cache.TreeCacheProvider | clustered (ip multicast), transactional | yes (replication) | yes (clock sync req.) |
類(lèi)或者集合映射的“<cache>元素”可以有下列形式:
<cacheusage="transactional|read-write|nonstrict-read-write|read-only" (1)/>| (1) | usage說(shuō)明了緩存的策略: transactional、 read-write、 nonstrict-read-write或 read-only。 |
另外(首選?), 你可以在hibernate.cfg.xml中指定<class-cache>和 <collection-cache> 元素。
這里的usage 屬性指明了緩存并發(fā)策略(cache concurrency strategy)。
如果你的應用程序只需讀取一個(gè)持久化類(lèi)的實(shí)例,而無(wú)需對其修改, 那么就可以對其進(jìn)行只讀 緩存。這是最簡(jiǎn)單,也是實(shí)用性最好的方法。甚至在集群中,它也能完美地運作。
<class name="eg.Immutable" mutable="false"><cache usage="read-only"/>....</class>
如果應用程序需要更新數據,那么使用讀/寫(xiě)緩存 比較合適。 如果應用程序要求“序列化事務(wù)”的隔離級別(serializable transaction isolation level),那么就決不能使用這種緩存策略。 如果在JTA環(huán)境中使用緩存,你必須指定hibernate.transaction.manager_lookup_class屬性的值, 通過(guò)它,Hibernate才能知道該應用程序中JTA的TransactionManager的具體策略。 在其它環(huán)境中,你必須保證在Session.close()、或Session.disconnect()調用前, 整個(gè)事務(wù)已經(jīng)結束。 如果你想在集群環(huán)境中使用此策略,你必須保證底層的緩存實(shí)現支持鎖定(locking)。Hibernate內置的緩存策略并不支持鎖定功能。
<class name="eg.Cat" .... ><cache usage="read-write"/>....<set name="kittens" ... ><cache usage="read-write"/>....</set></class>
如果應用程序只偶爾需要更新數據(也就是說(shuō),兩個(gè)事務(wù)同時(shí)更新同一記錄的情況很不常見(jiàn)),也不需要十分嚴格的事務(wù)隔離, 那么比較適合使用非嚴格讀/寫(xiě)緩存策略。如果在JTA環(huán)境中使用該策略, 你必須為其指定hibernate.transaction.manager_lookup_class屬性的值, 在其它環(huán)境中,你必須保證在Session.close()、或Session.disconnect()調用前, 整個(gè)事務(wù)已經(jīng)結束。
Hibernate的事務(wù)緩存策略提供了全事務(wù)的緩存支持, 例如對JBoss TreeCache的支持。這樣的緩存只能用于JTA環(huán)境中,你必須指定 為其hibernate.transaction.manager_lookup_class屬性。
沒(méi)有一種緩存提供商能夠支持上列的所有緩存并發(fā)策略。下表中列出了各種提供器、及其各自適用的并發(fā)策略。
無(wú)論何時(shí),當你給save()、update()或 saveOrUpdate()方法傳遞一個(gè)對象時(shí),或使用load()、 get()、list()、iterate() 或scroll()方法獲得一個(gè)對象時(shí), 該對象都將被加入到Session的內部緩存中。
當隨后flush()方法被調用時(shí),對象的狀態(tài)會(huì )和數據庫取得同步。 如果你不希望此同步操作發(fā)生,或者你正處理大量對象、需要對有效管理內存時(shí),你可以調用evict() 方法,從一級緩存中去掉這些對象及其集合。
ScrollableResult cats = sess.createQuery("from Cat as cat").scroll(); //a huge result setwhile ( cats.next() ) {Cat cat = (Cat) cats.get(0);doSomethingWithACat(cat);sess.evict(cat);}Session還提供了一個(gè)contains()方法,用來(lái)判斷某個(gè)實(shí)例是否處于當前session的緩存中。
如若要把所有的對象從session緩存中徹底清除,則需要調用Session.clear()。
對于二級緩存來(lái)說(shuō),在SessionFactory中定義了許多方法, 清除緩存中實(shí)例、整個(gè)類(lèi)、集合實(shí)例或者整個(gè)集合。
sessionFactory.evict(Cat.class, catId); //evict a particular CatsessionFactory.evict(Cat.class); //evict all CatssessionFactory.evictCollection("Cat.kittens", catId); //evict a particular collection of kittenssessionFactory.evictCollection("Cat.kittens"); //evict all kitten collectionsCacheMode參數用于控制具體的Session如何與二級緩存進(jìn)行交互。
CacheMode.NORMAL - 從二級緩存中讀、寫(xiě)數據。
CacheMode.GET - 從二級緩存中讀取數據,僅在數據更新時(shí)對二級緩存寫(xiě)數據。
CacheMode.PUT - 僅向二級緩存寫(xiě)數據,但不從二級緩存中讀數據。
CacheMode.REFRESH - 僅向二級緩存寫(xiě)數據,但不從二級緩存中讀數據。通過(guò) hibernate.cache.use_minimal_puts的設置,強制二級緩存從數據庫中讀取數據,刷新緩存內容。
如若需要查看二級緩存或查詢(xún)緩存區域的內容,你可以使用統計(Statistics) API。
Map cacheEntries = sessionFactory.getStatistics().getSecondLevelCacheStatistics(regionName).getEntries();
此時(shí),你必須手工打開(kāi)統計選項??蛇x的,你可以讓Hibernate更人工可讀的方式維護緩存內容。
hibernate.generate_statistics truehibernate.cache.use_structured_entries true
查詢(xún)的結果集也可以被緩存。只有當經(jīng)常使用同樣的參數進(jìn)行查詢(xún)時(shí),這才會(huì )有些用處。 要使用查詢(xún)緩存,首先你必須打開(kāi)它:
hibernate.cache.use_query_cache true
該設置將會(huì )創(chuàng )建兩個(gè)緩存區域 - 一個(gè)用于保存查詢(xún)結果集(org.hibernate.cache.StandardQueryCache); 另一個(gè)則用于保存最近查詢(xún)的一系列表的時(shí)間戳(org.hibernate.cache.UpdateTimestampsCache)。 請注意:在查詢(xún)緩存中,它并不緩存結果集中所包含的實(shí)體的確切狀態(tài);它只緩存這些實(shí)體的標識符屬性的值、以及各值類(lèi)型的結果。 所以查詢(xún)緩存通常會(huì )和二級緩存一起使用。
絕大多數的查詢(xún)并不能從查詢(xún)緩存中受益,所以Hibernate默認是不進(jìn)行查詢(xún)緩存的。如若需要進(jìn)行緩存,請調用 Query.setCacheable(true)方法。這個(gè)調用會(huì )讓查詢(xún)在執行過(guò)程中時(shí)先從緩存中查找結果, 并將自己的結果集放到緩存中去。
如果你要對查詢(xún)緩存的失效政策進(jìn)行精確的控制,你必須調用Query.setCacheRegion()方法, 為每個(gè)查詢(xún)指定其命名的緩存區域。
List blogs = sess.createQuery("from Blog blog where blog.blogger = :blogger").setEntity("blogger", blogger).setMaxResults(15).setCacheable(true).setCacheRegion("frontpages").list();如果查詢(xún)需要強行刷新其查詢(xún)緩存區域,那么你應該調用Query.setCacheMode(CacheMode.REFRESH)方法。 這對在其他進(jìn)程中修改底層數據(例如,不通過(guò)Hibernate修改數據),或對那些需要選擇性更新特定查詢(xún)結果集的情況特別有用。 這是對SessionFactory.evictQueries()的更為有效的替代方案,同樣可以清除查詢(xún)緩存區域。
前面我們已經(jīng)對集合進(jìn)行了足夠的討論。本段中,我們將著(zhù)重講述集合在運行時(shí)的事宜。
Hibernate定義了三種基本類(lèi)型的集合:
值數據集合
一對多關(guān)聯(lián)
多對多關(guān)聯(lián)
這個(gè)分類(lèi)是區分了不同的表和外鍵關(guān)系類(lèi)型,但是它沒(méi)有告訴我們關(guān)系模型的所有內容。 要完全理解他們的關(guān)系結構和性能特點(diǎn),我們必須同時(shí)考慮“用于Hibernate更新或刪除集合行數據的主鍵的結構”。 因此得到了如下的分類(lèi):
有序集合類(lèi)
集合(sets)
包(bags)
所有的有序集合類(lèi)(maps, lists, arrays)都擁有一個(gè)由<key>和 <index>組成的主鍵。 這種情況下集合類(lèi)的更新是非常高效的——主鍵已經(jīng)被有效的索引,因此當Hibernate試圖更新或刪除一行時(shí),可以迅速找到該行數據。
集合(sets)的主鍵由<key>和其他元素字段構成。 對于有些元素類(lèi)型來(lái)說(shuō),這很低效,特別是組合元素或者大文本、大二進(jìn)制字段; 數據庫可能無(wú)法有效的對復雜的主鍵進(jìn)行索引。 另一方面,對于一對多、多對多關(guān)聯(lián),特別是合成的標識符來(lái)說(shuō),集合也可以達到同樣的高效性能。( 附注:如果你希望SchemaExport為你的<set>創(chuàng )建主鍵, 你必須把所有的字段都聲明為not-null="true"。)
<idbag>映射定義了代理鍵,因此它總是可以很高效的被更新。事實(shí)上, <idbag>擁有著(zhù)最好的性能表現。
Bag是最差的。因為bag允許重復的元素值,也沒(méi)有索引字段,因此不可能定義主鍵。 Hibernate無(wú)法判斷出重復的行。當這種集合被更改時(shí),Hibernate將會(huì )先完整地移除 (通過(guò)一個(gè)(in a single DELETE))整個(gè)集合,然后再重新創(chuàng )建整個(gè)集合。 因此Bag是非常低效的。
請注意:對于一對多關(guān)聯(lián)來(lái)說(shuō),“主鍵”很可能并不是數據庫表的物理主鍵。 但就算在此情況下,上面的分類(lèi)仍然是有用的。(它仍然反映了Hibernate在集合的各數據行中是如何進(jìn)行“定位”的。)
根據我們上面的討論,顯然有序集合類(lèi)型和大多數set都可以在增加、刪除、修改元素中擁有最好的性能。
可論證的是對于多對多關(guān)聯(lián)、值數據集合而言,有序集合類(lèi)比集合(set)有一個(gè)好處。因為Set的內在結構, 如果“改變”了一個(gè)元素,Hibernate并不會(huì )更新(UPDATE)這一行。 對于Set來(lái)說(shuō),只有在插入(INSERT)和刪除(DELETE) 操作時(shí)“改變”才有效。再次強調:這段討論對“一對多關(guān)聯(lián)”并不適用。
注意到數組無(wú)法延遲載入,我們可以得出結論,list, map和idbags是最高效的(非反向)集合類(lèi)型,set則緊隨其后。 在Hibernate中,set應該時(shí)最通用的集合類(lèi)型,這時(shí)因為“set”的語(yǔ)義在關(guān)系模型中是最自然的。
但是,在設計良好的Hibernate領(lǐng)域模型中,我們通??梢钥吹礁嗟募鲜聦?shí)上是帶有inverse="true" 的一對多的關(guān)聯(lián)。對于這些關(guān)聯(lián),更新操作將會(huì )在多對一的這一端進(jìn)行處理。因此對于此類(lèi)情況,無(wú)需考慮其集合的更新性能。
在把bag扔進(jìn)水溝之前,你必須了解,在一種情況下,bag的性能(包括list)要比set高得多: 對于指明了inverse="true"的集合類(lèi)(比如說(shuō),標準的雙向的一對多關(guān)聯(lián)), 我們可以在未初始化(fetch)包元素的情況下直接向bag或list添加新元素! 這是因為Collection.add())或者Collection.addAll() 方法 對bag或者List總是返回true(這點(diǎn)與與Set不同)。因此對于下面的相同代碼來(lái)說(shuō),速度會(huì )快得多。
Parent p = (Parent) sess.load(Parent.class, id);Child c = new Child();c.setParent(p);p.getChildren().add(c); //no need to fetch the collection!sess.flush();
偶爾的,逐個(gè)刪除集合類(lèi)中的元素是相當低效的。Hibernate并沒(méi)那么笨, 如果你想要把整個(gè)集合都刪除(比如說(shuō)調用list.clear()),Hibernate只需要一個(gè)DELETE就搞定了。
假設我們在一個(gè)長(cháng)度為20的集合類(lèi)中新增加了一個(gè)元素,然后再刪除兩個(gè)。 Hibernate會(huì )安排一條INSERT語(yǔ)句和兩條DELETE語(yǔ)句(除非集合類(lèi)是一個(gè)bag)。 這當然是顯而易見(jiàn)的。
但是,假設我們刪除了18個(gè)數據,只剩下2個(gè),然后新增3個(gè)。則有兩種處理方式:
逐一的刪除這18個(gè)數據,再新增三個(gè);
刪除整個(gè)集合類(lèi)(只用一句DELETE語(yǔ)句),然后增加5個(gè)數據。
Hibernate還沒(méi)那么聰明,知道第二種選擇可能會(huì )比較快。 (也許讓Hibernate不這么聰明也是好事,否則可能會(huì )引發(fā)意外的“數據庫觸發(fā)器”之類(lèi)的問(wèn)題。)
幸運的是,你可以強制使用第二種策略。你需要取消原來(lái)的整個(gè)集合類(lèi)(解除其引用), 然后再返回一個(gè)新的實(shí)例化的集合類(lèi),只包含需要的元素。有些時(shí)候這是非常有用的。
顯然,一次性刪除并不適用于被映射為inverse="true"的集合。
沒(méi)有監測和性能參數而進(jìn)行優(yōu)化是毫無(wú)意義的。Hibernate為其內部操作提供了一系列的示意圖,因此可以從 每個(gè)SessionFactory抓取其統計數據。
你可以有兩種方式訪(fǎng)問(wèn)SessionFactory的數據記錄,第一種就是自己直接調用 sessionFactory.getStatistics()方法讀取、顯示統計數據。
此外,如果你打開(kāi)StatisticsService MBean選項,那么Hibernate則可以使用JMX技術(shù) 發(fā)布其數據記錄。你可以讓?xiě)弥兴械?tt class="literal">SessionFactory同時(shí)共享一個(gè)MBean,也可以每個(gè) SessionFactory分配一個(gè)MBean。下面的代碼即是其演示代碼:
// MBean service registration for a specific SessionFactoryHashtable tb = new Hashtable();tb.put("type", "statistics");tb.put("sessionFactory", "myFinancialApp");ObjectName on = new ObjectName("hibernate", tb); // MBean object nameStatisticsService stats = new StatisticsService(); // MBean implementationstats.setSessionFactory(sessionFactory); // Bind the stats to a SessionFactoryserver.registerMBean(stats, on); // Register the Mbean on the server// MBean service registration for all SessionFactory‘sHashtable tb = new Hashtable();tb.put("type", "statistics");tb.put("sessionFactory", "all");ObjectName on = new ObjectName("hibernate", tb); // MBean object nameStatisticsService stats = new StatisticsService(); // MBean implementationserver.registerMBean(stats, on); // Register the MBean on the serverTODO:仍需要說(shuō)明的是:在第一個(gè)例子中,我們直接得到和使用MBean;而在第二個(gè)例子中,在使用MBean之前 我們則需要給出SessionFactory的JNDI名,使用hibernateStatsBean.setSessionFactoryJNDIName("my/JNDI/Name") 得到SessionFactory,然后將MBean保存于其中。
你可以通過(guò)以下方法打開(kāi)或關(guān)閉SessionFactory的監測功能:
在配置期間,將hibernate.generate_statistics設置為true或false;
在運行期間,則可以可以通過(guò)sf.getStatistics().setStatisticsEnabled(true) 或hibernateStatsBean.setStatisticsEnabled(true)
你也可以在程序中調用clear()方法重置統計數據,調用logSummary() 在日志中記錄(info級別)其總結。
Hibernate提供了一系列數據記錄,其記錄的內容包括從最基本的信息到與具體場(chǎng)景的特殊信息。所有的測量值都可以由 Statistics接口進(jìn)行訪(fǎng)問(wèn),主要分為三類(lèi):
使用Session的普通數據記錄,例如打開(kāi)的Session的個(gè)數、取得的JDBC的連接數等;
實(shí)體、集合、查詢(xún)、緩存等內容的統一數據記錄
和具體實(shí)體、集合、查詢(xún)、緩存相關(guān)的詳細數據記錄
例如:你可以檢查緩存的命中成功次數,緩存的命中失敗次數,實(shí)體、集合和查詢(xún)的使用概率,查詢(xún)的平均時(shí)間等。請注意 Java中時(shí)間的近似精度是毫秒。Hibernate的數據精度和具體的JVM有關(guān),在有些平臺上其精度甚至只能精確到10秒。
你可以直接使用getter方法得到全局數據記錄(例如,和具體的實(shí)體、集合、緩存區無(wú)關(guān)的數據),你也可以在具體查詢(xún)中通過(guò)標記實(shí)體名、 或HQL、SQL語(yǔ)句得到某實(shí)體的數據記錄。請參考Statistics、EntityStatistics、 CollectionStatistics、SecondLevelCacheStatistics、 和QueryStatistics的API文檔以抓取更多信息。下面的代碼則是個(gè)簡(jiǎn)單的例子:
Statistics stats = HibernateUtil.sessionFactory.getStatistics();double queryCacheHitCount = stats.getQueryCacheHitCount();double queryCacheMissCount = stats.getQueryCacheMissCount();double queryCacheHitRatio =queryCacheHitCount / (queryCacheHitCount + queryCacheMissCount);log.info("Query Hit ratio:" + queryCacheHitRatio);EntityStatistics entityStats =stats.getEntityStatistics( Cat.class.getName() );long changes =entityStats.getInsertCount()+ entityStats.getUpdateCount()+ entityStats.getDeleteCount();log.info(Cat.class.getName() + " changed " + changes + "times" );如果你想得到所有實(shí)體、集合、查詢(xún)和緩存區的數據,你可以通過(guò)以下方法獲得實(shí)體、集合、查詢(xún)和緩存區列表: getQueries()、getEntityNames()、 getCollectionRoleNames()和 getSecondLevelCacheRegionNames()。
聯(lián)系客服