• 16、【转】Hibernate 原汁原味的四种抓取策略


    最近在研究 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 略

        Order :

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

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

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

  • 相关阅读:
    Android自动化测试解决方案
    Oracle数据库的DML命令的处理过程详解
    Oracle数据库的BULK COLLECT用法之批量增删改
    建设DevOps能力,实现业务敏捷
    强大的C# Expression在一个函数求导问题中的简单运用
    Visual Studio 11开发者预览版发布(附下载)
    js table隔行变色
    编译原理语法推导树
    巧用数据库归档技术解决性能下降问题
    编译原理正规式和有限自动机
  • 原文地址:https://www.cnblogs.com/zhangbaowei/p/5038858.html
Copyright © 2020-2023  润新知