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策略:

Java代码  
 
<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策略:

Java代码 
@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策略。请看示例代码:

Java代码   收藏代码
  1. //call select from stock  
  2. Stock stock = (Stock)session.get(Stock.class114);  
  3. Set sets = stock.getStockDailyRecords();  
  4.   
  5. //call select from stock_daily_record  
  6. for ( Iterator iter = sets.iterator();iter.hasNext(); ) {  
  7.   StockDailyRecord sdr = (StockDailyRecord) iter.next();  
  8.   System.out.println(sdr.getDailyRecordId());  
  9.   System.out.println(sdr.getDate());  
  10. }  


输出如下:

Java代码   收藏代码
  1. Hibernate:  
  2.   select ...from demo.stock  
  3.   where stock0_.STOCK_ID=?  
  4.    
  5. Hibernate:  
  6.   select ...from demo.stock_daily_record  
  7.   where stockdaily0_.STOCK_ID=?  


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

Java代码   收藏代码
  1. Hibernate:  
  2.   select ...  
  3.   from  
  4.     demo.stock stock0_  
  5.   left outer join  
  6.     demo.stock_daily_record stockdaily1_  
  7.     on stock0_.STOCK_ID=stockdaily1_.STOCK_ID  
  8.   where  
  9.     stock0_.STOCK_ID=?  


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

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

Java代码   收藏代码
  1. Hibernate:  
  2.   select ...  
  3.   from demo.stock stock0_  
  4.    
  5. Hibernate:  
  6.   select ...  
  7.   from  
  8.     demo.stock_daily_record stockdaily0_  
  9.   where  
  10.     stockdaily0_.STOCK_ID in (  
  11.       select  
  12.         stock0_.STOCK_ID  
  13.       from  
  14.         demo.stock stock0_  
  15.     )  


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

Java代码   收藏代码
  1. Hibernate:  
  2.   select ...from demo.stock  
  3.   where stock0_.STOCK_ID=?  
  4.    
  5. Hibernate:  
  6.   select ...from demo.stock_daily_record  
  7.   where stockdaily0_.STOCK_ID=?  


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

Java代码   收藏代码
  1. List<Stock> list = session.createQuery("from Stock").list();  
  2. for(Stock stock : list){  
  3.   Set sets = stock.getStockDailyRecords();  
  4.   for ( Iterator iter = sets.iterator();iter.hasNext(); ) {  
  5.     StockDailyRecord sdr = (StockDailyRecord) iter.next();  
  6.     System.out.println(sdr.getDailyRecordId());  
  7.     System.out.println(sdr.getDate());  
  8.   }  
  9. }  


没有batch-size fetching策略时,输出:

Java代码   收藏代码
  1. Hibernate:  
  2.   select ...  
  3.   from demo.stock stock0_  
  4.    
  5. Hibernate:  
  6.   select ...  
  7.   from demo.stock_daily_record stockdaily0_  
  8.   where stockdaily0_.STOCK_ID=?  
  9.    
  10. Hibernate:  
  11.   select ...  
  12.   from demo.stock_daily_record stockdaily0_  
  13.   where stockdaily0_.STOCK_ID=?  
  14.    
  15. 继续重复select语句....这取决于数据表中有多少条stock记录。  


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

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

Java代码   收藏代码
  1. Hibernate:  
  2.   select ...  
  3.   from demo.stock stock0_  
  4.    
  5. Hibernate:  
  6.   select ...  
  7.   from demo.stock_daily_record stockdaily0_  
  8.   where  
  9.     stockdaily0_.STOCK_ID in (  
  10.       ?, ?, ?, ?, ?, ?, ?, ?, ?, ?  
  11.     )  


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

抓取优化


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

结论


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

你可能感兴趣的:(Hibernate4)