1.抓取策略(Fetching strategies)

  抓取策略(fetching strategy) 是指:當應用程序需要在(Hibernate實體對象圖的)關聯關系間進行導航的時候, Hibernate如何獲取關聯對象的策略。抓取策略可以在O/R映射的元數據中聲明,也可以在特定的HQL 或條件查詢(Criteria Query)中重載聲明。

  Hibernate3 定義了如下幾種抓取策略:

  • 連接抓?。↗oin fetching) - Hibernate通過 在SELECT語句使用OUTER JOIN(外連接)來 獲得對象的關聯實例或者關聯集合。

  • 查詢抓?。⊿elect fetching) - 另外發送一條 SELECT 語句抓取當前對象的關聯實體或集合。除非你顯式的指定lazy="false"禁止 延遲抓?。╨azy fetching),否則只有當你真正訪問關聯關系的時候,才會執行第二條select語句。

  • 子查詢抓?。⊿ubselect fetching) - 另外發送一條SELECT 語句抓取在前面查詢到(或者抓取到)的所有實體對象的關聯集合。除非你顯式的指定lazy="false" 禁止延遲抓?。╨azy fetching),否則只有當你真正訪問關聯關系的時候,才會執行第二條select語句。

  • 批量抓?。˙atch fetching) - 對查詢抓取的優化方案, 通過指定一個主鍵或外鍵列表,Hibernate使用單條SELECT語句獲取一批對象實例或集合。

  Hibernate會區分下列各種情況:

  • Immediate fetching,立即抓取 - 當宿主被加載時,關聯、集合或屬性被立即抓取。

  • Lazy collection fetching,延遲集合抓取- 直到應用程序對集合進行了一次操作時,集合才被抓取。(對集合而言這是默認行為。)

  • "Extra-lazy" collection fetching,"Extra-lazy"集合抓取 -對集合類中的每個元素而言,都是直到需要時才去訪問數據庫。除非絕對必要,Hibernate不會試圖去把整個集合都抓取到內存里來(適用于非常大的集合)。

  • Proxy fetching,代理抓取 - 對返回單值的關聯而言,當其某個方法被調用,而非對其關鍵字進行get操作時才抓取。

  • "No-proxy" fetching,非代理抓取 - 對返回單值的關聯而言,當實例變量被訪問的時候進行抓取。與上面的代理抓取相比,這種方法沒有那么“延遲”得厲害(就算只訪問標識符,也會導致關聯抓取)但是更加透明,因為對應用程序來說,不再看到proxy。這種方法需要在編譯期間進行字節碼增強操作,因此很少需要用到。

  • Lazy attribute fetching,屬性延遲加載 - 對屬性或返回單值的關聯而言,當其實例變量被訪問的時候進行抓取。需要編譯期字節碼強化,因此這一方法很少是必要的。

  這里有兩個正交的概念:關聯何時被抓取,以及被如何抓?。〞捎檬裁礃拥腟QL語句)。不要混淆它們!我們使用抓取來改善性能。我們使用延遲來定義一些契約,對某特定類的某個脫管的實例,知道有哪些數據是可以使用的。

  2. 操作延遲加載的關聯

  默認情況下,Hibernate 3對集合使用延遲select抓取,對返回單值的關聯使用延遲代理抓取。對幾乎是所有的應用而言,其絕大多數的關聯,這種策略都是有效的。

  注意:假若你設置了hibernate.default_batch_fetch_size,Hibernate會對延遲加載采取批量抓取優化措施(這種優化也可能會在更細化的級別打開)。

  然而,你必須了解延遲抓取帶來的一個問題。在一個打開的Hibernate session上下文之外調用延遲集合會導致一次意外。比如:

Java代碼
  1. s = sessions.openSession();  
  2. Transaction tx = s.beginTransaction();  
  3.               
  4. User u = (User) s.createQuery("from User u where u.name=:userName")  
  5.     .setString("userName", userName).uniqueResult();  
  6. Map permissions = u.getPermissions();  
  7.   
  8. tx.commit();  
  9. s.close();  
  10.   
  11. Integer accessLevel = (Integer) permissions.get("accounts");  // Error!  

  在Session關閉后,permessions集合將是未實例化的、不再可用,因此無法正常載入其狀態。 Hibernate對脫管對象不支持延遲實例化. 這里的修改方法是:將permissions讀取數據的代碼 移到tx.commit()之前。

  除此之外,通過對關聯映射指定lazy="false",我們也可以使用非延遲的集合或關聯。但是, 對絕大部分集合來說,更推薦使用延遲方式抓取數據。如果在你的對象模型中定義了太多的非延遲關聯,Hibernate最終幾乎需要在每個事務中載入整個數據庫到內存中!

  但是,另一方面,在一些特殊的事務中,我們也經常需要使用到連接抓?。ㄋ旧砩暇褪欠茄舆t的),以代替查詢抓取。 下面我們將會很快明白如何具體的定制Hibernate中的抓取策略。在Hibernate3中,具體選擇哪種抓取策略的機制是和選擇 單值關聯或集合關聯相一致的。

  3.  調整抓取策略(Tuning fetch strategies)

  查詢抓?。J的)在N+1查詢的情況下是極其脆弱的,因此我們可能會要求在映射文檔中定義使用連接抓?。?/p>

XML/HTML代碼
  1. <set name="permissions"   
  2.             fetch="join">  
  3.     <key column="userId"/>  
  4.     <one-to-many class="Permission"/>  
  5. </set  
  6. <many-to-one name="mother" class="Cat" fetch="join"/>  

  在映射文檔中定義的抓取策略將會對以下列表條目產生影響:

  • 通過get()或load()方法取得數據。

  • 只有在關聯之間進行導航時,才會隱式的取得數據。

  • 條件查詢

  • 使用了subselect抓取的HQL查詢

  不管你使用哪種抓取策略,定義為非延遲的類圖會被保證一定裝載入內存。注意這可能意味著在一條HQL查詢后緊跟著一系列的查詢。

  通常情況下,我們并不使用映射文檔進行抓取策略的定制。更多的是,保持其默認值,然后在特定的事務中, 使用HQL的左連接抓?。╨eft join fetch) 對其進行重載。這將通知 Hibernate在第一次查詢中使用外部關聯(outer join),直接得到其關聯數據。 在條件查詢 API中,應該調用 setFetchMode(FetchMode.JOIN)語句。

  也許你喜歡僅僅通過條件查詢,就可以改變get() 或 load()語句中的數據抓取策略。例如:

Java代碼
  1. User user = (User) session.createCriteria(User.class)  
  2.                 .setFetchMode("permissions", FetchMode.JOIN)  
  3.                 .add( Restrictions.idEq(userId) )  
  4.                 .uniqueResult();  

 ?。ㄟ@就是其他ORM解決方案的“抓取計劃(fetch plan)”在Hibernate中的等價物。)

  截然不同的一種避免N+1次查詢的方法是,使用二級緩存。

  4.. 單端關聯代理(Single-ended association proxies)

  在Hinerbate中,對集合的延遲抓取的采用了自己的實現方法。但是,對于單端關聯的延遲抓取,則需要采用 其他不同的機制。單端關聯的目標實體必須使用代理,Hihernate在運行期二進制級(通過優異的CGLIB庫), 為持久對象實現了延遲載入代理。

  默認的,Hibernate3將會為所有的持久對象產生代理(在啟動階段),然后使用他們實現 多對一(many-to-one)關聯和一對一(one-to-one) 關聯的延遲抓取。

  在映射文件中,可以通過設置proxy屬性為目標class聲明一個接口供代理接口使用。 默認的,Hibernate將會使用該類的一個子類。 注意:被代理的類必須實現一個至少包可見的默認構造函數,我們建議所有的持久類都應擁有這樣的構造函數

  在如此方式定義一個多態類的時候,有許多值得注意的常見性的問題,例如:

XML/HTML代碼
  1. <class name="Cat" proxy="Cat">  
  2.     ......  
  3.     <subclass name="DomesticCat">  
  4.         .....  
  5.     </subclass>  
  6. </class>  

  首先,Cat實例永遠不可以被強制轉換為DomesticCat, 即使它本身就是DomesticCat實例。

Java代碼
  1. Cat cat = (Cat) session.load(Cat.class, id);  // instantiate a proxy (does not hit the db)  
  2. if ( cat.isDomesticCat() ) {                  // hit the db to initialize the proxy  
  3.     DomesticCat dc = (DomesticCat) cat;       // Error!  
  4.     ....  
  5. }  

  其次,代理的“==”可能不再成立。

Java代碼
  1. Cat cat = (Cat) session.load(Cat.class, id);            // instantiate a Cat proxy  
  2. DomesticCat dc =   
  3.         (DomesticCat) session.load(DomesticCat.class, id);  // acquire new DomesticCat proxy!  
  4. System.out.println(cat==dc);                            // false  

  雖然如此,但實際情況并沒有看上去那么糟糕。雖然我們現在有兩個不同的引用,分別指向這兩個不同的代理對象, 但實際上,其底層應該是同一個實例對象:

Java代碼
  1. cat.setWeight(11.0);  // hit the db to initialize the proxy  
  2. System.out.println( dc.getWeight() );  // 11.0  

  第三,你不能對“final類”或“具有final方法的類”使用CGLIB代理。

  最后,如果你的持久化對象在實例化時需要某些資源(例如,在實例化方法、默認構造方法中), 那么代理對象也同樣需要使用這些資源。實際上,代理類是持久化類的子類。

  這些問題都源于Java的單根繼承模型的天生限制。如果你希望避免這些問題,那么你的每個持久化類必須實現一個接口, 在此接口中已經聲明了其業務方法。然后,你需要在映射文檔中再指定這些接口。例如:

XML/HTML代碼
  1. <class name="CatImpl" proxy="Cat">  
  2.     ......  
  3.     <subclass name="DomesticCatImpl" proxy="DomesticCat">  
  4.         .....  
  5.     </subclass>  
  6. </class>  

  這里CatImpl實現了Cat接口, DomesticCatImpl實現DomesticCat接口。 在load()、iterate()方法中就會返回 Cat和DomesticCat的代理對象。 (注意list()并不會返回代理對象。)

Java代碼
  1. Cat cat = (Cat) session.load(CatImpl.class, catid);  
  2. Iterator iter = session.iterate("from CatImpl as cat where cat.name='fritz'");  
  3. Cat fritz = (Cat) iter.next();  

  這里,對象之間的關系也將被延遲載入。這就意味著,你應該將屬性聲明為Cat,而不是CatImpl。

  但是,在有些方法中是不需要使用代理的。例如:

  • equals()方法,如果持久類沒有重載equals()方法。

  • hashCode()方法,如果持久類沒有重載hashCode()方法。

  • 標志符的getter方法。

  Hibernate將會識別出那些重載了equals()、或hashCode()方法的持久化類。

  若選擇lazy="no-proxy"而非默認的lazy="proxy",我們可以避免類型轉換帶來的問題。然而,這樣我們就需要編譯期字節碼增強,并且所有的操作都會導致立刻進行代理初始化。

  5.實例化集合和代理(Initializing collections and proxies)

  在Session范圍之外訪問未初始化的集合或代理,Hibernate將會拋出LazyInitializationException異常。 也就是說,在分離狀態下,訪問一個實體所擁有的集合,或者訪問其指向代理的屬性時,會引發此異常。

  有時候我們需要保證某個代理或者集合在Session關閉前就已經被初始化了。 當然,我們可以通過強行調用cat.getSex()或者cat.getKittens().size()之類的方法來確保這一點。 但是這樣的程序會造成讀者的疑惑,也不符合通常的代碼規范。

  靜態方法Hibernate.initialized() 為你的應用程序提供了一個便捷的途徑來延遲加載集合或代理。 只要它的Session處于open狀態,Hibernate.initialize(cat) 將會為cat強制對代理實例化。 同樣,Hibernate.initialize( cat.getKittens() ) 對kittens的集合具有同樣的功能。

  還有另外一種選擇,就是保持Session一直處于open狀態,直到所有需要的集合或代理都被載入。 在某些應用架構中,特別是對于那些使用Hibernate進行數據訪問的代碼,以及那些在不同應用層和不同物理進程中使用Hibernate的代碼。 在集合實例化時,如何保證Session處于open狀態經常會是一個問題。有兩種方法可以解決此問題:

  • 在一個基于Web的應用中,可以利用servlet過濾器(filter),在用戶請求(request)結束、頁面生成 結束時關閉Session(這里使用了在展示層保持打開Session模式(Open Session in View)), 當然,這將依賴于應用框架中異常需要被正確的處理。在返回界面給用戶之前,乃至在生成界面過程中發生異常的情況下, 正確關閉Session和結束事務將是非常重要的, 請參見Hibernate wiki上的"Open Session in View"模式,你可以找到示例。

  • 在一個擁有單獨業務層的應用中,業務層必須在返回之前,為web層“準備”好其所需的數據集合。這就意味著 業務層應該載入所有表現層/web層所需的數據,并將這些已實例化完畢的數據返回。通常,應用程序應該 為web層所需的每個集合調用Hibernate.initialize()(這個調用必須發生咱session關閉之前); 或者使用帶有FETCH從句,或FetchMode.JOIN的Hibernate查詢, 事先取得所有的數據集合。如果你在應用中使用了Command模式,代替Session Facade , 那么這項任務將會變得簡單的多。

  • 你也可以通過merge()或lock()方法,在訪問未實例化的集合(或代理)之前, 為先前載入的對象綁定一個新的Session。 顯然,Hibernate將不會,也不應該自動完成這些任務,因為這將引入一個特殊的事務語義。

  有時候,你并不需要完全實例化整個大的集合,僅需要了解它的部分信息(例如其大?。?、或者集合的部分內容。

  你可以使用集合過濾器得到其集合的大小,而不必實例化整個集合:

  ( (Integer) s.createFilter( collection, "select count(*)" ).list().get(0) ).intValue()

  這里的createFilter()方法也可以被用來有效的抓取集合的部分內容,而無需實例化整個集合:

  s.createFilter( lazyCollection, "").setFirstResult(0).setMaxResults(10).list();

  6.使用批量抓?。║sing batch fetching)

  Hibernate可以充分有效的使用批量抓取,也就是說,如果僅一個訪問代理(或集合),那么Hibernate將不載入其他未實例化的代理。 批量抓取是延遲查詢抓取的優化方案,你可以在兩種批量抓取方案之間進行選擇:在類級別和集合級別。

  類/實體級別的批量抓取很容易理解。假設你在運行時將需要面對下面的問題:你在一個Session中載入了25個 Cat實例,每個Cat實例都擁有一個引用成員owner, 其指向Person,而Person類是代理,同時lazy="true"。 如果你必須遍歷整個cats集合,對每個元素調用getOwner()方法,Hibernate將會默認的執行25次SELECT查詢, 得到其owner的代理對象。這時,你可以通過在映射文件的Person屬性,顯式聲明batch-size,改變其行為:

  <class name="Person" batch-size="10">...</class>

  隨之,Hibernate將只需要執行三次查詢,分別為10、10、 5。

  你也可以在集合級別定義批量抓取。例如,如果每個Person都擁有一個延遲載入的Cats集合, 現在,Sesssion中載入了10個person對象,遍歷person集合將會引起10次SELECT查詢, 每次查詢都會調用getCats()方法。如果你在Person的映射定義部分,允許對cats批量抓取, 那么,Hibernate將可以預先抓取整個集合。請看例子:

XML/HTML代碼
  1. <class name="Person">  
  2.     <set name="cats" batch-size="3">  
  3.         ...  
  4.     </set>  
  5. </class>  

  如果整個的batch-size是3(筆誤?),那么Hibernate將會分四次執行SELECT查詢, 按照3、3、3、1的大小分別載入數據。這里的每次載入的數據量還具體依賴于當前Session中未實例化集合的個數。

  如果你的模型中有嵌套的樹狀結構,例如典型的帳單-原料結構(bill-of-materials pattern),集合的批量抓取是非常有用的。 (盡管在更多情況下對樹進行讀取時,嵌套集合(nested set)或原料路徑(materialized path)(××) 是更好的解決方法。)

  7. 使用子查詢抓?。║sing subselect fetching)

  假若一個延遲集合或單值代理需要抓取,Hibernate會使用一個subselect重新運行原來的查詢,一次性讀入所有的實例。這和批量抓取的實現方法是一樣的,不會有破碎的加載。

  8.使用延遲屬性抓?。║sing lazy property fetching)

  Hibernate3對單獨的屬性支持延遲抓取,這項優化技術也被稱為組抓?。╢etch groups)。 請注意,該技術更多的屬于市場特性。在實際應用中,優化行讀取比優化列讀取更重要。但是,僅載入類的部分屬性在某些特定情況下會有用,例如在原有表中擁有幾百列數據、數據模型無法改動的情況下。

  可以在映射文件中對特定的屬性設置lazy,定義該屬性為延遲載入。

XML/HTML代碼
  1. <class name="Document">  
  2.        <id name="id">  
  3.         <generator class="native"/>  
  4.     </id>  
  5.     <property name="name" not-null="true" length="50"/>  
  6.     <property name="summary" not-null="true" length="200" lazy="true"/>  
  7.     <property name="text" not-null="true" length="2000" lazy="true"/>  
  8. </class>  

  屬性的延遲載入要求在其代碼構建時加入二進制指示指令(bytecode instrumentation),如果你的持久類代碼中未含有這些指令, Hibernate將會忽略這些屬性的延遲設置,仍然將其直接載入。

  你可以在Ant的Task中,進行如下定義,對持久類代碼加入“二進制指令。”

XML/HTML代碼
  1. <target name="instrument" depends="compile">  
  2.     <taskdef name="instrument" classname="org.hibernate.tool.instrument.InstrumentTask">  
  3.         <classpath path="${jar.path}"/>  
  4.         <classpath path="${classes.dir}"/>  
  5.         <classpath refid="lib.class.path"/>  
  6.     </taskdef>  
  7.   
  8.     <instrument verbose="true">  
  9.         <fileset dir="${testclasses.dir}/org/hibernate/auction/model">  
  10.             <include name="*.class"/>  
  11.         </fileset>  
  12.     </instrument>  
  13. </target>  

  還有一種可以優化的方法,它使用HQL或條件查詢的投影(projection)特性,可以避免讀取非必要的列, 這一點至少對只讀事務是非常有用的。它無需在代碼構建時“二進制指令”處理,因此是一個更加值得選擇的解決方法。

  有時你需要在HQL中通過抓取所有屬性,強行抓取所有內容。

除非特別注明,雞啄米文章均為原創
轉載請標明本文地址:http://www.028keji.com/software/716.html
2017年4月10日
作者:雞啄米 分類:軟件開發 瀏覽: 評論:0