`
youngerbaby
  • 浏览: 111908 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

【转载】Hibernate 原汁原味的四种抓取策略

    博客分类:
  • Java
阅读更多

看到了一篇对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 :

    

  1. public   class  Customer {  
  2.     private   long  id;  
  3.     private  String name;  
  4.     private  Set<Order> orders;  
  5.     // getter/setter 略   
  6. }  
 

    Order :

    

  1. public   class  Order {  
  2.     private   long  id;  
  3.     private  String name;  
  4.     private  Customer customer;  
  5.     // getter/setter略   
  6. }  
 

Order 的映射文件是不变的, 放在这 :

  1. < hibernate-mapping   package = "com.purking.strategys.endUpOne" >   
  2.     < class   name = "Order"   table = "Order_Table" >   
  3.         < id   name = "id" >   
  4.             < generator   class = "native"   />   
  5.         </ id >   
  6.         < property   name = "name"   length = "20"   column = "Order_Name"   />   
  7.         < many-to-one   name = "customer"   
  8.                      class = "Customer"   
  9.                      lazy = "proxy"   
  10.                      fetch = "select"   
  11.                      column = "Cus_ID"   
  12.                      cascade = "save-update"   />   
  13.     </ class >   
  14. </ 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 >   

 

我们使用如此查询语句 :

  1. Customer c1 = (Customer)session.get(Customer. class , 11l);  
  2. 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 =?  

 

在此, Hibernate 使用了 left outer join 连接两个表以一条 SQL 语句将 Order 集合

给初始化了;


 
查询抓取(Select fetching)

    查询抓取, 这种策略是在集合抓取的时候的默认策略, 即如果集合需要初始化, 那么

会重新发出一条 SQL 语句进行查询; 这是集合默认的抓取策略, 也就是我们常会出现

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

配置文件 :

  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.             < key   column = "Cus_ID"   />   
  11.             < one-to-many   class = "Order"   />   
  12.         </ set >   
  13.     </ class >   
  14. </ hibernate-mapping >   

 查询语句不变, 看看 Hibernate 发出的 SQL 语句:

  1. Hibernate:   
  2.     select  
  3.         customer0_.id as id0_0_,  
  4.         customer0_.Cus_Name as Cus2_0_0_   
  5.     from  
  6.         Customer_Table customer0_   
  7.     where  
  8.         customer0_.id =?  
  9. Hibernate:   
  10.     select  
  11.         orders0_.Cus_ID as Cus3_1_,  
  12.         orders0_.id as id1_,  
  13.         orders0_.id as id1_0_,  
  14.         orders0_.Order_Name as Order2_1_0_,  
  15.         orders0_.Cus_ID as Cus3_1_0_   
  16.     from  
  17.         Order_Table orders0_   
  18.     where  
  19.         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 >   

 

测试的语句有变化 :

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

 

这个时候再来看看 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.         )  

 

是不是发出的 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 发出的 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 =?  

原本需要四次 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> 但是这里没有提供这个属性, 可能是因为

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

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics