Hibernate学习手记(8) - 抓取策略

Hibernate3定义了以下几种抓取策略:

连接抓取(Join fetching) - Hibernate通过在SELECT语句使用OUTER JOIN(外连接)来获得对象的关联实例或者关联集合

查询抓取(Select fetching) - 另外发送一条 SELECT 语句抓取当前对象的关联实体或集合。这也就是通过外键的方式来执行数据库的查询。除非你显式的指定lazy="false"禁止延迟抓取(lazy fetching),否则只有当你真正访问关联关系的时候,才会执行第二条select语句

子查询抓取(Subselect fetching) - 另外发送一条SELECT 语句抓取在前面查询到(或者抓取到)的所有实体对象的关联集合。与查询抓取的区别在于它所用的SELECT语句的方式为子查询,而不是通过外连接。除非你显式的指定lazy="false" 禁止延迟抓取(lazy fetching),否则只有当你真正访问关联关系的时候,才会执行第二条select语句

批量抓取(Batch fetching) - 对查询抓取的优化方案, 通过指定一个主键或外键列表,Hibernate使用单条SELECT语句获取一批对象实例或集合

抓取关联对象的时机上有以下几点:

立即抓取,延迟代理抓取,延迟属性抓取,延迟集合抓取

默认对对象关联是采取延迟加载,普通属性采用立即抓取。

对于延迟加载,需要注意的是,对于延迟对象的使用必须在Session关闭之前进行,否则会报LazyInitalizationException

对于集合类型的关联,在默认情况下会使用延迟集合加载的抓取时机,而对于返回单值类型的关联在默认情况下会使用延迟代理抓取的抓取时机。

A.应用于对象关联实例(默认是延迟加载,默认fetch="select"

<many-to-one name="......"  lazy="true/false"  fetch="select" />

B.应用于对象关联集合(默认是延迟加载,默认fetch="select",默认batch-size="1" 

<class name="......" table="......" batch-size="1"> 

......

<set name="......"  lazy="true/false"  fetch="select"> 

......

</set>

</class>

(注意:设置fetch“join”,会导致lazy失效;抓取粒度batch-size并不是越多越好,其值超过50之后,对性能并没有多大改善反而无谓地消耗内存,一般来说,被关联表数据较少则可设置小一点,3~20,如果比较大则可以设到30~50

以下CustomerOrder的一个双向一对多例子来使用四种抓取策略看看他们的不同之处;

Customer :

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

Order 的映射文件是不变的:

<hibernate-mapping >

<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

    连接抓取使用连接抓取可以将原本需要查询两次(或多次)表的多次查询整合到只需要一次查询即可完成举个例子我们在初始化一个含有一对多关系的CustomerOrder的时候会先查询Customer,找到需要的Customer然后再根据Customer.id 到 Order 表中查询将Order 集合初始化那么在此完成初始化则需要发送至少两条 SQL 语句而如果使用 join 查询的话其会根据需要查询的Customer.id, 将 Customer 表与 Order 表连接起来进行查询,仅仅一条 SQL 语句就可以将需要的数据全部查询回来;使用连接抓取的配置文件 :

<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>

我们使用如此查询语句 :

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

c1.getOrders().size(); 

Hibernate 发出的 SQL 语句为 :

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

这就是重新发出一条 SQL 语句初始化了 Orders 集合;

子查询抓取(Subselect fetching 

子查询抓取另外发送一条SELECT 语句抓取在前面查询到(或者抓取到)的所有实体对象的关联集合这个理解起来有点糊涂举个例子 如果你使用 Query 查询出了个 Customer 实体由于开启了懒加载,那么他们的 Orders 都没有被初始化那么我现在手初始化一个CustomerOrders ,此时由于我选的是Subselect fetching策略,所以Hibernate 会将前面查询到的实体对象(4 Customer)的关联集合(<set name="orders" fetch="subselect" /> )使用一条 Select 语句一次性抓取回来这样减少了与数据库的交互次数一次将每个对象的集合都给初始化了

[他是如何这么智能的呢原来,他是将上一次查询的 SQL 语句作为这一次查询的 SQL语句的 where 子查询所以上次查询到几个对象,那么这次就初始化几个对象的集合----- 正因为如此所以 subselect 只在 <set> 集合中出现 ];

配置文件:

<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>

测试的语句有变化 :

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 语句 :

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 策略改为一次 条的批量 select 查询举个例子:还是借用 Subselect fetching 的例子,我查询出了个 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 集合;

[他是如何做到的呢通过一个主键或外键列表做到的他将 个 Customer 根据batch-size 分成了两组一组有三个 Customer id 值的列表,第二组只有一个,在初始化 orders 集合的时候就是根据这两个列表来初始化的]

配置文件 :

<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 语句 :

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 查询出 个 Customer , 我们想一次性初始化 个 Customer的 IdCard 怎么办设置 <class name="IdCard" batch-size="4" > , 可能我们想设置的地方是 <one-to-one batch-size> 但是这里没有提供这个属性可能是因为如果设置了不好理解吧..

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值