【轉載】Hibernate 原汁原味的四種抓取策略

看到了一篇對hibernate抓取策略講解比較詳細且清晰的文章,轉一下

 

原文:http://blog.csdn.net/Purking/archive/2009/12/30/5109151.aspx

 

最近在研究 Hibernate 的性能優化的時候碰到了"抓取策略", 由於以前沒有詳細的研究過,

    所以到處找資料, 但是無論從一些講 Hibernate 書籍,還是他人 Blog 中都沒有找到詳細

    介紹 Hibernate 文檔中所說的原汁原味的抓取策略, 綜合懶加載等等特性混在了一起, 所

    以在這自己在借鑑了他人的基礎上研究了下原汁原味的 Hibernate 四種"抓取策略";

  • 連接抓取(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 語句獲取一批對象實例或集合

    這是文檔中的四種抓取策略, 我用 Customer 與 Order 的一個雙向一對多例子來使用四種

    抓取策略看看他們的不同之處;

    Customer :

    

public   class  Customer {       private   long  id;       private  String name;       private  Set<Order> orders;       // getter/setter 略    }  
public class Customer { private long id; private String name; private Set<Order> orders; // getter/setter 略 }  

    Order :

    

public   class  Order {       private   long  id;       private  String name;       private  Customer customer;       // getter/setter略    }  
public class Order { private long id; private String name; private Customer customer; // getter/setter略 }  

Order 的映射文件是不變的, 放在這 :

< hibernate-mapping   package = "com.purking.strategys.endUpOne" >        < class   name = "Order"   table = "Order_Table" >            < id   name = "id" >                < generator   class = "native"   />            </ id >            < property   name = "name"   length = "20"   column = "Order_Name"   />            < many-to-one   name = "customer"                         class = "Customer"                         lazy = "proxy"                         fetch = "select"                         column = "Cus_ID"                         cascade = "save-update"   />        </ class >    </ hibernate-mapping >   
<hibernate-mapping package="com.purking.strategys.endUpOne"> <class name="Order" table="Order_Table"> <id name="id"> <generator class="native" /> </id> <property name="name" length="20" column="Order_Name" /> <many-to-one name="customer" class="Customer" lazy="proxy" fetch="select" column="Cus_ID" cascade="save-update" /> </class> </hibernate-mapping>  

連接抓取(Join fetching)

    連接抓取, 使用連接抓取可以將原本需要查詢兩次(或多次)表的多次查詢 整合到只需

要一次查詢即可完成, 舉個例子, 我們在初始化一個含有一對多關係的 Customer 與

Order 的時候, 會先查詢 Customer 表,找到需要的 Customer , 然後再根據

Customer.id 到 Order 表中查詢將Order 集合初始化, 那麼在此完成初始化則需要

發送至少兩條 SQL 語句, 而如果使用 join 查詢的話, 其會根據需要查詢的

Customer.id, 將 Customer 表與 Order 表連接起來進行查詢,僅僅一條 SQL 語

句就可以將需要的數據全部查詢回來;

使用連接抓取的配置文件 :

  1. < hibernate-mapping   package = "com.purking.strategys.endUpOne" >   
  2.     < class   name = "Customer"   table = "Customer_Table"   lazy = "true" >   
  3.         < id   name = "id" >   
  4.             < generator   class = "native"   />   
  5.         </ id >   
  6.         < property   name = "name"   length = "20"   column = "Cus_Name"   />   
  7.         < set   name = "orders"   
  8.              inverse = "true"   
  9.              fetch = "join"   //---- Here  
  10.             <!-- 這裏關閉懶加載是爲了試驗明顯 -->   
  11.              lazy = "false" >   
  12.             < key   column = "Cus_ID"   />   
  13.             < one-to-many   class = "Order"   />   
  14.         </ set >   
  15.     </ class >   
  16. </ hibernate-mapping >   

<hibernate-mapping package="com.purking.strategys.endUpOne"> <class name="Customer" table="Customer_Table" lazy="true"> <id name="id"> <generator class="native" /> </id> <property name="name" length="20" column="Cus_Name" /> <set name="orders" inverse="true" fetch="join" //---- Here <!-- 這裏關閉懶加載是爲了試驗明顯 --> lazy="false"> <key column="Cus_ID" /> <one-to-many class="Order" /> </set> </class> </hibernate-mapping>

 

我們使用如此查詢語句 :

  1. Customer c1 = (Customer)session.get(Customer. class , 11l);  
  2. c1.getOrders().size();  

Customer c1 = (Customer)session.get(Customer.class, 11l); c1.getOrders().size();

 

Hibernate 發出的 SQL 語句爲 : 

  1. select  
  2.     customer0_.id as id0_1_,  
  3.     customer0_.Cus_Name as Cus2_0_1_,  
  4.     orders1_.Cus_ID as Cus3_3_,  
  5.     orders1_.id as id3_,  
  6.     orders1_.id as id1_0_,  
  7.     orders1_.Order_Name as Order2_1_0_,  
  8.     orders1_.Cus_ID as Cus3_1_0_   
  9. from  
  10.     Customer_Table customer0_   
  11. left outer join  
  12.     Order_Table orders1_   
  13.         on customer0_.id = orders1_ .Cus_ID   
  14. where  
  15.     customer0_.id =?  

select customer0_.id as id0_1_, customer0_.Cus_Name as Cus2_0_1_, orders1_.Cus_ID as Cus3_3_, orders1_.id as id3_, orders1_.id as id1_0_, orders1_.Order_Name as Order2_1_0_, orders1_.Cus_ID as Cus3_1_0_ from Customer_Table customer0_ left outer join Order_Table orders1_ on customer0_.id=orders1_.Cus_ID where customer0_.id=?

 

在此, Hibernate 使用了 left outer join 連接兩個表以一條 SQL 語句將 Order 集合

給初始化了;


 
查詢抓取(Select fetching)

    查詢抓取, 這種策略是在集合抓取的時候的默認策略, 即如果集合需要初始化, 那麼

會重新發出一條 SQL 語句進行查詢; 這是集合默認的抓取策略, 也就是我們常會出現

N+1次查詢的查詢策略;

配置文件 :

< hibernate-mapping   package = "com.purking.strategys.endUpOne" >        < class   name = "Customer"   table = "Customer_Table"   lazy = "true" >            < id   name = "id" >                < generator   class = "native"   />            </ id >            < property   name = "name"   length = "20"   column = "Cus_Name"   />            < set   name = "orders"                 inverse = "true"                 fetch = "select" >                < key   column = "Cus_ID"   />                < one-to-many   class = "Order"   />            </ set >        </ class >    </ hibernate-mapping >   
<hibernate-mapping package="com.purking.strategys.endUpOne"> <class name="Customer" table="Customer_Table" lazy="true"> <id name="id"> <generator class="native" /> </id> <property name="name" length="20" column="Cus_Name" /> <set name="orders" inverse="true" fetch="select"> <key column="Cus_ID" /> <one-to-many class="Order" /> </set> </class> </hibernate-mapping>
 查詢語句不變, 看看 Hibernate 發出的 SQL 語句:

Hibernate:        select           customer0_.id as id0_0_,           customer0_.Cus_Name as Cus2_0_0_        from           Customer_Table customer0_        where           customer0_.id =?   Hibernate:        select           orders0_.Cus_ID as Cus3_1_,           orders0_.id as id1_,           orders0_.id as id1_0_,           orders0_.Order_Name as Order2_1_0_,           orders0_.Cus_ID as Cus3_1_0_        from           Order_Table orders0_        where           orders0_.Cus_ID =?  
Hibernate: select customer0_.id as id0_0_, customer0_.Cus_Name as Cus2_0_0_ from Customer_Table customer0_ where customer0_.id=? Hibernate: select orders0_.Cus_ID as Cus3_1_, orders0_.id as id1_, orders0_.id as id1_0_, orders0_.Order_Name as Order2_1_0_, orders0_.Cus_ID as Cus3_1_0_ from Order_Table orders0_ where orders0_.Cus_ID=?  

這就是, 重新發出一條 SQL 語句, 初始化了 Orders 集合;

子查詢抓取(Subselect fetching)

    子查詢抓取, 另外發送一條SELECT 語句抓取在前面查詢到(或者抓取到)的所有實

體對象的關聯集合. 這個理解起來有點糊塗, 舉個例子 : 如果你使用 Query 查詢出了

4 個 Customer 實體, 由於開啓了懶加載,那麼他們的 Orders 都沒有被初始化, 那麼我

現在手動初始化一個Customer 的 Orders ,此時由於我選的是 Subselect fetching

策略,所以 Hibernate 會將前面查詢到的實體對象(4 個 Customer)的關聯集合(在 

<set name="orders" fetch="subselect" /> )使用一條 Select 語句一次性抓取

回來, 這樣減少了與數據庫的交互次數, 一次將每個對象的集合都給初始化了;

[他是如何這麼智能的呢? 原來,他是將上一次查詢的 SQL 語句作爲這一次查詢的 SQL

語句的 where 子查詢, 所以上次查詢到幾個對象,那麼這次就初始化幾個對象的集

合----- 正因爲如此, 所以 subselect 只在 <set> 集合中出現 ];

配置文件: 

  1. < hibernate-mapping   package = "com.purking.strategys.endUpOne" >   
  2.     < class   name = "Customer"   table = "Customer_Table"   lazy = "true" >   
  3.         < id   name = "id" >   
  4.             < generator   class = "native"   />   
  5.         </ id >   
  6.         < property   name = "name"   length = "20"   column = "Cus_Name"   />   
  7.         < set   name = "orders"   
  8.              inverse = "true"   
  9.              fetch = "subselect"   
  10.              lazy = "true" >   
  11.             < key   column = "Cus_ID"   />   
  12.             < one-to-many   class = "Order"   />   
  13.         </ set >   
  14.     </ class >   
  15. </ hibernate-mapping >   

<hibernate-mapping package="com.purking.strategys.endUpOne"> <class name="Customer" table="Customer_Table" lazy="true"> <id name="id"> <generator class="native" /> </id> <property name="name" length="20" column="Cus_Name" /> <set name="orders" inverse="true" fetch="subselect" lazy="true"> <key column="Cus_ID" /> <one-to-many class="Order" /> </set> </class> </hibernate-mapping>

 

測試的語句有變化 :

  1. List results = session  
  2. .createQuery("From Customer c where c.id in (11,14,17,20)" )  
  3. .list();  
  4. // 這裏的四個 id 是我數據庫中已經準備好的數據   
  5. Customer c0 = (Customer)results.get(0 );  
  6. c0.getOrders().size();  

List results = session .createQuery("From Customer c where c.id in (11,14,17,20)") .list(); // 這裏的四個 id 是我數據庫中已經準備好的數據 Customer c0 = (Customer)results.get(0); c0.getOrders().size();

 

這個時候再來看看 Hibernate 發出了什麼樣的 SQL 語句 :

  1. Hibernate:   
  2.     select  
  3.         customer0_.id as id0_,  
  4.         customer0_.Cus_Name as Cus2_0_   
  5.     from  
  6.         Customer_Table customer0_   
  7.     where  
  8.         customer0_.id in (  
  9.             11 , 14 , 17 , 20  
  10.         )  
  11. Hibernate:   
  12.     select  
  13.         orders0_.Cus_ID as Cus3_1_,  
  14.         orders0_.id as id1_,  
  15.         orders0_.id as id1_0_,  
  16.         orders0_.Order_Name as Order2_1_0_,  
  17.         orders0_.Cus_ID as Cus3_1_0_   
  18.     from  
  19.         Order_Table orders0_   
  20.     where  
  21.         orders0_.Cus_ID in (  
  22.             select  
  23.                 customer0_.id   
  24.             from  
  25.                 Customer_Table customer0_   
  26.             where  
  27.                 customer0_.id in (  
  28.                     11 , 14 , 17 , 20  
  29.                 )  
  30.         )  

Hibernate: select customer0_.id as id0_, customer0_.Cus_Name as Cus2_0_ from Customer_Table customer0_ where customer0_.id in ( 11 , 14 , 17 , 20 ) Hibernate: select orders0_.Cus_ID as Cus3_1_, orders0_.id as id1_, orders0_.id as id1_0_, orders0_.Order_Name as Order2_1_0_, orders0_.Cus_ID as Cus3_1_0_ from Order_Table orders0_ where orders0_.Cus_ID in ( select customer0_.id from Customer_Table customer0_ where customer0_.id in ( 11 , 14 , 17 , 20 ) )

 

是不是發出的 SQL 語句形式與這個抓取策略的名字一樣? Hibernate 的命名很清晰的;

批量抓取(Batch fetching)  

     批量抓取: "對查詢抓取的優化方案,通過指定一個主鍵或外鍵 列表,Hibernate使用

單條SELECT語句獲取一批對象實例或集合", 也就是說其本質與 select fetching 是

一樣的,只不過將一次一條的 select 策略改爲一次 N 條的批量 select 查詢; 舉個例

子 : 還是借用 Subselect fetching 的例子,我查詢出了 4 個 Customer 實體,

Orders 開啓了懶加載, 所以我現在來手動初始化一個 Customer 的 orders 屬性,

這種策略本質上就是 select fetching,所以如此設置 :

<set name="orders" fetch="select" batch-size="3" /> 那麼此時我初始化

一個 Customer 的 orders 集合的時候, Hibernate 還是發出了一條 SQL 語句,

不過這條 SQL 與是通過指定了 Order 表中的 Customer_ID 外鍵列表(2個), 這個

時候 Hibernate 會以一條 SQL 語句初始化 batch-size 指定的數量的 orders 集合;

[他是如何做到的呢? 通過一個主鍵或外鍵 列表 做到的, 他將 4 個 Customer 根據

batch-size 分成了兩組, 一組有三個 Customer id 值的列表,第二組只有一個,

在初始化 orders 集合的時候就是根據這兩個列表來初始化的]

配置文件 :

  1. <hibernate-mapping  package = "com.purking.strategys.endUpOne" >  
  2.     <class  name= "Customer"  table= "Customer_Table"  lazy= "true" >  
  3.         <id name="id" >  
  4.             <generator class = "native"  />  
  5.         </id>  
  6.         <property name="name"  length= "20"  column= "Cus_Name"  />  
  7.         <set name="orders"   
  8.              inverse="true"   
  9.              fetch="select"   
  10.              lazy="true"   
  11.              batch-size="3" >  
  12.             <key column="Cus_ID"  />  
  13.             <one-to-many class = "Order"  />  
  14.         </set>  
  15.     </class >  
  16. </hibernate-mapping>  

<hibernate-mapping package="com.purking.strategys.endUpOne"> <class name="Customer" table="Customer_Table" lazy="true"> <id name="id"> <generator class="native" /> </id> <property name="name" length="20" column="Cus_Name" /> <set name="orders" inverse="true" fetch="select" lazy="true" batch-size="3"> <key column="Cus_ID" /> <one-to-many class="Order" /> </set> </class> </hibernate-mapping>

 

在此,我關閉了集合默認的懶加載, 更有利於試驗結果測試代碼不變,

再來看看 Hibernate 發出的 SQL 語句 :

  1. Hibernate:   
  2.     select  
  3.         customer0_.id as id0_,  
  4.         customer0_.Cus_Name as Cus2_0_   
  5.     from  
  6.         Customer_Table customer0_   
  7.     where  
  8.         customer0_.id in (  
  9.             11 , 14 , 17 , 20  
  10.         )  
  11. Hibernate:   
  12.     select  
  13.         orders0_.Cus_ID as Cus3_1_,  
  14.         orders0_.id as id1_,  
  15.         orders0_.id as id1_0_,  
  16.         orders0_.Order_Name as Order2_1_0_,  
  17.         orders0_.Cus_ID as Cus3_1_0_   
  18.     from  
  19.         Order_Table orders0_   
  20.     where  
  21.         orders0_.Cus_ID in (  
  22.             ?, ?, ?  
  23.         )  
  24. Hibernate:   
  25.     select  
  26.         orders0_.Cus_ID as Cus3_1_,  
  27.         orders0_.id as id1_,  
  28.         orders0_.id as id1_0_,  
  29.         orders0_.Order_Name as Order2_1_0_,  
  30.         orders0_.Cus_ID as Cus3_1_0_   
  31.     from  
  32.         Order_Table orders0_   
  33.     where  
  34.         orders0_.Cus_ID =?  
Hibernate: select customer0_.id as id0_, customer0_.Cus_Name as Cus2_0_ from Customer_Table customer0_ where customer0_.id in ( 11 , 14 , 17 , 20 ) Hibernate: select orders0_.Cus_ID as Cus3_1_, orders0_.id as id1_, orders0_.id as id1_0_, orders0_.Order_Name as Order2_1_0_, orders0_.Cus_ID as Cus3_1_0_ from Order_Table orders0_ where orders0_.Cus_ID in ( ?, ?, ? ) Hibernate: select orders0_.Cus_ID as Cus3_1_, orders0_.id as id1_, orders0_.id as id1_0_, orders0_.Order_Name as Order2_1_0_, orders0_.Cus_ID as Cus3_1_0_ from Order_Table orders0_ where orders0_.Cus_ID=?

原本需要四次 Select 的查詢, 由於 Batch-size=3 只用了兩次

就完成了;


總結: 

    好了, 這裏的四種抓取策略說明完了, 來全局看一下, 通過例子可以看出, 這四種抓取

策略並不是所有的情況都合適的, 例如, 如果我需要初始化的是一個單獨的實體, 那

麼 subselect 對其就沒有效果,因爲其本身就只需要查詢一個對象, 所以 : 

  1. Join fetching , Select fetching 與 Batch-size 可以爲單個實體的抓取進
    行性能優化;
  2. Join fetching , Select fetching ,Subselect fetching , Batch fetching
    都可以爲集合的抓取進行性能優化;

注: 這裏對於單個實體可以使用 Batch-size 可能會有點疑惑, 其實在 <class > 上是

具有 Batch-size 抓取策略的; 試想, 使用一個如果是一對一關係呢? 例如 Customer 

與 IdCard, 利用 HQL 查詢出 4 個 Customer , 我們想一次性初始化 4 個 Customer 

的 IdCard 怎麼辦, 設置 <class name="IdCard" batch-size="4" > , 可能我們

想設置的地方是 <one-to-one batch-size> 但是這裏沒有提供這個屬性, 可能是因爲

如果設置了不好理解吧..

發佈了39 篇原創文章 · 獲贊 0 · 訪問量 3735
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章