(十五)Hibernate之性能優化之抓取策略

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

通過配置抓取策略可以直接影響Session的get()和load()方法的查詢效率
Hibernate3 定義瞭如下幾種抓取策略
    * 連接抓取(Join fetching) - Hibernate通過 在SELECT語句使用OUTER JOIN(外連接)來 獲得對象的關聯實例或者關聯集合。
    *查詢抓取(Select fetching) - 另外發送一條 SELECT 語句抓取當前對象的關聯實體或集合。除非你顯式的指定lazy="false"禁止 延遲抓取(lazy fetching),否則只有當你真正訪問關聯關係的時候,纔會執行第二條select語句。
    *子查詢抓取(Subselect fetching) - 另外發送一條SELECT 語句抓取在前面查詢到(或者抓取到)的所有實體對象的關聯集合。除非你顯式的指定lazy="false" 禁止延遲抓取(lazy fetching),否則只有當你真正訪問關聯關係的時候,纔會執行第二條select語句。
    *批量抓取(Batch fetching) - 對查詢抓取的優化方案, 通過指定一個主鍵或外鍵列表,Hibernate使用單條SELECT語句獲取一批對象實例或集合。

Hibernate抓取策略會區分下列各種情況

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

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

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

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

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

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

這裏有兩個正交的概念:關聯何時被抓取,以及被如何抓取(會採用什麼樣的SQL語句)。不要混淆它們!我們使用抓取來改善性能。我們使用延遲來定義一些契約,對某特定類的某個脫管的實例,知道有哪些數據是可以使用的。 

1.操作延遲加載的關聯

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

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

然而,你必須瞭解延遲抓取帶來的一個問題。在一個打開的Hibernate session上下文之外調用延遲集合會導致一次意外。比如: 
Java代碼  收藏代碼
  1. <span style="font-size: large;">s = sessions.openSession();    
  2. Transaction tx = s.beginTransaction();                  
  3. User u = (User) s.createQuery("from User u where u.name=:userName")      .setString("userName", userName).uniqueResult();    
  4. Map permissions = u.getPermissions();    
  5. tx.commit();   
  6. s.close();    
  7. Integer accessLevel = (Integer) permissions.get("accounts");  // Error!</span>  
 在Session關閉後,permessions集合將是未實例化的、不再可用,因此無法正常載入其狀態。 Hibernate對脫管對象不支持延遲實例化. 這裏的修改方法是:將permissions讀取數據的代碼 移到tx.commit()之前。

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

但是,另一方面,在一些特殊的事務中,我們也經常需要使用到連接抓取(它本身上就是非延遲的),以代替查詢抓取。 下面我們將會很快明白如何具體的定製Hibernate中的抓取策略。在Hibernate3中,具體選擇哪種抓取策略的機制是和選擇 單值關聯或集合關聯相一致的。


 

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

查詢抓取(默認的)在N+1查詢的情況下是極其脆弱的,因此我們可能會要求在映射文檔中定義使用連接抓取:  

 

 

Java代碼  收藏代碼
  1. <span style="font-size: large;"><set name="permissions"     fetch="join">   
  2. <key column="userId"/>       
  3. <one-to-many class="Permission"/>   
  4. </set>   
  5. <many-to-one name="mother" class="Cat" fetch="join"/> </span>  
 在映射文檔中定義的抓取策略將會對以下列表條目產生影響:通過get()或load()方法取得數據。只有在關聯之間進行導航時,纔會隱式的取得數據。

 

條件查詢,使用了subselect抓取的HQL查詢

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

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

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

Java代碼  收藏代碼
  1. <span style="font-size: large;">User user = (User) session.createCriteria(User.class) .  
  2.                    setFetchMode("permissions", FetchMode.JOIN).  
  3.                    add( Restrictions.idEq(userId) ).uniqueResult(); </span>  

 

  (這就是其他ORM解決方案的“抓取計劃(fetch plan)”在Hibernate中的等價物。)截然不同的一種避免N+1次查詢的方法是,使用二級緩存。


示例配置(主要有兩種情況):
  1)單端關聯(<many-to-one>、<one-to-many>)上的抓取
可以給單端關聯的映射元素添加fetch屬性。fetch屬性有兩個可選值。
a).select:作爲默認值,它的策略是黨需要使用到關聯關係對象的數據時,另外單獨發送一條select語句抓取當前對象的關聯對象的數據。即延時加載。
b).join:它的策略是在同一條select語句使用連接李艾獲得對象的數據和它關聯的對象的數據,此時關聯對象的延遲加載失效.
以下是單端關聯上fetch=join的一個配置示例
Java代碼  收藏代碼
  1. <span style="font-size: large;"><?xml version="1.0" encoding="UTF-8"?>  
  2. <!DOCTYPE hibernate-mapping PUBLIC  
  3.         "-//Hibernate/Hibernate Mapping DTD 3.0//EN"  
  4.         "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">  
  5.   
  6. <hibernate-mapping>  
  7.     <class name="com.javacrazyer.domain.Product" table="product">  
  8.         <id name="id">  
  9.             <generator class="native"/>  
  10.         </id>  
  11.         <version name="version"/>  
  12.           
  13.         <property name="name"/>  
  14.         <property name="unitCost" column="unit_cost"/>  
  15.         <property name="pubTime" column="pub_time"/>  
  16.           
  17.         <!-- 映射多對一 -->  
  18.         <many-to-one name="cate" column="cate_id" fetch="join"/>  
  19.           
  20.     </class>  
  21. </hibernate-mapping></span>  
 
在應用程序中加載某個實體product的數據時,會使用內連接把它關聯的Category實體也加載上來,即類似下面的SQL語句:

Sql代碼  收藏代碼
  1. <span style="font-size: large;">select tab1.xx,tab1.yy,tab2.aa,tab2.cc   
  2.                    from product tab1   
  3.                    inner join category tab2   
  4.                    on tab1.cate_id=tab2.id   
  5.                    where product_id=?</span>  
 2)集合屬性上抓取策略
 在集合屬性的映射元素上可以添加fetch屬性,他有三個可選值
a).select:作爲默認值,它的策略是黨需要使用所關聯集合的數據時,另外單獨發送一條select語句抓取當前對象的關聯集合,即延時加載
b).join:在同一條select語句使用連接來獲得對象的關聯集合,此時關聯集合上的lazy會失效
c).subselect:另外發送一體哦啊查詢語句(或者子查詢語句)抓取在前面查詢到的所有實體對象的關聯集合.這個策略對HQL的查詢也起作用.
以下是集合屬性上fetch=subselect的示例
Java代碼  收藏代碼
  1. <span style="font-size: large;"><?xml version="1.0" encoding="UTF-8"?>  
  2. <!DOCTYPE hibernate-mapping PUBLIC  
  3.         "-//Hibernate/Hibernate Mapping DTD 3.0//EN"  
  4.         "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">  
  5.   
  6. <hibernate-mapping>  
  7.     <class name="com.javacrazyer.domain.Category" table="category" batch-size="10">  
  8.         <id name="id">  
  9.             <generator class="native"/>  
  10.         </id>  
  11.         <version name="version"/>  
  12.         <property name="name"/>  
  13.         <property name="description"/>  
  14.           
  15.         <!-- 映射集合屬性 -->  
  16.         <bag name="productList" inverse="true" fetch="subselect">  
  17.             <key column="cate_id"/>  
  18.             <!-- 映射一對多 -->  
  19.             <one-to-many class="com.javacrazyer.domain.Product"/>  
  20.         </bag>  
  21.     </class>  
  22. </hibernate-mapping></span>  
當使用get()或load()方法加載一個Category實體數據時,它對關聯的Product集合屬性先延遲加載,當真正需要使用 Product集合屬性中的數據時,纔再發送一條SQL語句來抓取數據; 當使用HQL語句加載多個CategoryShiite數據時,對它們關聯的Product集合屬性先延遲加載,當真正需要使用Product集合屬性的數據時,纔會再發送一條子查詢語句來抓取相應的數據


批量抓取
(1)在Hibernate中,對於關聯抓取,可以定義每次抓取數據的數量,批量地將數據載入內存,減少與數據庫交互的次數。在應用程序中可以定義車間默認的關聯抓取數量。在
<hibernate-configuration>
    <session-factory>
         <property name-"default_batch_fetch_size">2</property>
    </session-factory>
</hibernate-configuration>

(2)在映射定義文件中,可能在元素class中使用屬性batch-size爲持久化類指定批量抓取的數量。同樣,如果要在集合中使用指定的批量,可以在集合元素set(list、bag等)中使用屬性batch-size指定。如果同進採用了默認的批量抓取配置,又爲持久化類或集合配置了特定的抓取數量,則類或集合的特定配置將覆蓋配置文件中的默認批量抓取屬性。
<hibernate-mapping package="com">
    <class name="Classes">
        <id name="id">
            <generator class="native"></generator>
        </id>
        <property name="name"/>
        <set name="students" inverse="true" batch-size="3">
            <key column="classesid"/>
            <one-to-many class="Student"/>
        </set>
    </class>
</hibernate-mapping>
發佈了7 篇原創文章 · 獲贊 6 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章