Hibernate4性能之Fetching策略


Hibernate有一些fetching策略,来优化Hibernate所生成的select语句,以尽可能地提高效率。在映射关系中声明fetching策略,定义Hibernate怎样获取其相关的集合和实体。

影响关系映射抓取的cfg配置:

hibernate.max_fetch_depth
为单向关联(一对一, 多对一)的外连接抓取(outer join fetch)树设置最大深度. 值为0意味着将关闭默认的外连接抓取.
取值 建议在0到3之间取值

hibernate.default_batch_fetch_size
为Hibernate关联的批量抓取设置默认数量.
取值 建议的取值为4, 8, 和16

如果你的数据库支持ANSI, Oracle或Sybase风格的外连接, 外连接抓取通常能通过限制往返数据库次数 (更多的工作交由数据库自己来完成)来提高效率. 外连接抓取允许在单个SELECTSQL语句中, 通过many-to-one, one-to-many, many-to-many和one-to-one关联获取连接对象的整个对象图.
将hibernate.max_fetch_depth设为0能在全局 范围内禁止外连接抓取. 设为1或更高值能启用one-to-one和many-to-one外连接关联的外连接抓取, 它们通过 fetch="join"来映射.

Hibernate有四种fetching策略,当同时配置了JPA的fetch时,会优先使用Hibernate的注解:
  • fetch-“join”:禁用延迟加载,总是立即加载所有的集合和实体。
  • fetch-“select”(默认):延迟加载所有的集合和实体。
  • fetch-“subselect”:将其集合组织到一个子查询语句中。
  • batch-size=”N”:获取上限为“N”个的集合或实体,没有记录。


在XML映射文件中声明fetch策略:
...
<hibernate-mapping>
  <class name="com.java.demo.Stock" table="stock">
    <set name="stockDailyRecords" cascade="all" inverse="true"
        table="stock_daily_record" batch-size="10" fetch="select">
      <key>
        <column name="STOCK_ID" not-null="true" />
      </key>
      <one-to-many class="com.java.demo.StockDailyRecord" />
    </set>
  </class>
</hibernate-mapping>


以标注的形式声明fetch策略:
...
@Entity
@Table(name = "stock")
public class Stock implements Serializable{
...
  @OneToMany(fetch = FetchType.LAZY, mappedBy = "stock")
  @Cascade(CascadeType.ALL)
  @Fetch(FetchMode.SELECT)
  @BatchSize(size = 10)
  public Set<StockDailyRecord> getStockDailyRecords() {
    return this.stockDailyRecords;
  }
...
}



下面探讨fetch策略如何影响到Hibernate生成的SQL语句
1、@Fetch(FetchMode.SELECT)
另外发送一条SELECT语句抓取当前对象的关联实体或集合。除非你显式的指定lazy="false"禁止延迟抓取(lazy fetching),否则只有当你真正访问关联关系的时候,才会执行第二条select语句。
这是默认的fetch策略。请看示例代码:

  //call select from stock
  Stock stock = (Stock)session.get(Stock.class, 114);
  Set sets = stock.getStockDailyRecords();
 
  //call select from stock_daily_record
  for ( Iterator iter = sets.iterator();iter.hasNext(); ) {
    StockDailyRecord sdr = (StockDailyRecord) iter.next();
    System.out.println(sdr.getDailyRecordId());
    System.out.println(sdr.getDate());
  }

输出如下:
Hibernate:
  select ...from demo.stock
  where stock0_.STOCK_ID=?
 
Hibernate:
  select ...from demo.stock_daily_record
  where stockdaily0_.STOCK_ID=?

2、@Fetch(FetchMode.JOIN)
“join”类型fetch策略将禁用延迟加载所有相关的集合。Hibernate通过在SELECT语句使用OUTER JOIN(外连接)来获得对象的关联实例或者关联集合。
执行上面的代码输出如下:

Hibernate:
  select ...
  from
    demo.stock stock0_
  left outer join
    demo.stock_daily_record stockdaily1_
    on stock0_.STOCK_ID=stockdaily1_.STOCK_ID
  where
    stock0_.STOCK_ID=?

Hibernate只生成一个select语句,当Stock被初始化时,它获取所有其相关的集合。

3、@Fetch(FetchMode.SUBSELECT)
Fetching策略能够将其所有相关集合放在一个子select语句中。
另外发送一条SELECT 语句抓取在前面查询到(或者抓取到)的所有实体对象的关联集合。除非你显式的指定lazy="false" 禁止延迟抓取(lazy fetching),否则只有当你真正访问关联关系的时候,才会执行第二条select语句。
当通过Query等接口查询多个实体时,如果指定fetch="subselect"则将通过子查询获取集合
还是运行上面代码输出:

Hibernate:
  select ...
  from demo.stock stock0_
 
Hibernate:
  select ...
  from
    demo.stock_daily_record stockdaily0_
  where
    stockdaily0_.STOCK_ID in (
      select
        stock0_.STOCK_ID
      from
        demo.stock stock0_
    )

4、@BatchSize(size=10)
对查询抓取的优化方案,通过指定一个主键或外键列表,Hibernate使用单条SELECT语句获取一批对象实例或集合。  
当通过Query等接口查询多个实体时,如果指定farm的batch-size="……"则将通过使用单条SELECT语句获取一批对象实例或集合。
可指定全局批量抓取策略: hibernate.default_batch_fetch_size,取值:建议的取值为4, 8, 和16。
执行上面代码输出如下:

Hibernate:
  select ...from demo.stock
  where stock0_.STOCK_ID=?
 
Hibernate:
  select ...from demo.stock_daily_record
  where stockdaily0_.STOCK_ID=?

batch-size什么也没做。请看下面的解释:
batch-size fetching策略并不是定义集合中有多少记录被预加载。相反,它其实是定义有多少集合应该被加载。

  List<Stock> list = session.createQuery("from Stock").list();
  for(Stock stock : list){
    Set sets = stock.getStockDailyRecords();
    for ( Iterator iter = sets.iterator();iter.hasNext(); ) {
      StockDailyRecord sdr = (StockDailyRecord) iter.next();
      System.out.println(sdr.getDailyRecordId());
      System.out.println(sdr.getDate());
    }
  }

没有batch-size fetching策略时,输出:
Hibernate:
  select ...
  from demo.stock stock0_
 
Hibernate:
  select ...
  from demo.stock_daily_record stockdaily0_
  where stockdaily0_.STOCK_ID=?
 
Hibernate:
  select ...
  from demo.stock_daily_record stockdaily0_
  where stockdaily0_.STOCK_ID=?
 
继续重复select语句....这取决于数据表中有多少条stock记录。

如果在数据库中有20条股票记录,Hibernate默认的fetching策略将生成20+1个select语句,这将对数据库造成一定的冲击。

启用batch-size=”10” fetching策略时,输出:

Hibernate:
  select ...
  from demo.stock stock0_
 
Hibernate:
  select ...
  from demo.stock_daily_record stockdaily0_
  where
    stockdaily0_.STOCK_ID in (
      ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
    )

现在,Hibernate将预提取集合,使用select in语句。如果在数据库中有20条股票记录,Hibernate将生成3个select语句

抓取优化

集合N+1:
可以使用batch-size来减少获取次数,即如batch-size=”10”,则是N/10+1。
开启二级缓存。
对于集合比较小且一定会用到的可采用fetch=”join”,这样只需一条语句。

结论

Fetching策略非常具有弹性,是一个优化Hibernate查询的非常重要的技巧。不过如果用在错误的地方,那将会是一个灾难。

你可能感兴趣的:(Hibernate,fetch,subselect,batch-size)