抓取策略(Fetching strategies)
抓取策略(fetching strategy)是指:当应用程序使用关联对象的时候,Hibernate如何获取关联对象的策略。
抓取策略可以在O/R映射的元数据中声明,也可以在特定的HQL或条件查询(Criteria Query)中重载声明。
在映射文档中定义的抓取策略将会对以下列表条目产生影响:
通过get()或load()方法取得数据。
只有在关联之间进行导航时,才会隐式的取得数据。
注意:对HQL 无效
Hibernate3 定义了如下几种抓取策略:
连接抓取(fetch="join") - Hibernate通过 在SELECT语句使用OUTER JOIN(外连接)来获得对象的关联实例或者关联集合。
查询抓取(fetch="select") - 另外发送一条 SELECT 语句抓取当前对象的关联实体或集合。
除非你显式的指定lazy="false"禁止延迟抓取(lazy fetching),否则只有当你真正访问关联关系的时候,才会执行第二条select语句。
子查询抓取(fetch="subselect") - 另外发送一条SELECT 语句抓取在前面查询到(或者抓取到)的所有实体对象的关联集合。
除非你显式的指定lazy="false" 禁止延迟抓取(lazy fetching),否则只有当你真正访问关联关系的时候,才会执行第二条select语句。
批量抓取(Batch fetching) - 对查询抓取的优化方案, 通过指定一个主键或外键列表,Hibernate使用单条SELECT语句获取一批对象实例或集合。 <property name="default_batch_fetch_size">10</property>
hibernate抓取策略(单端代理的批量抓取)
(一)保持默认,同fetch="select",如:
<many-to-one name="classes" column="classesid" fetch="select"/>
fetch="select",另外发送一条select语句抓取当前对象关联实体或集合
(二)设置fetch="join",如:
<many-to-one name="classes" column="classesid" fetch="join"/>
fetch="join",hibernate会通过select语句使用外连接来加载其关联实体或集合,此时lazy会失效
hibernate抓取策略(集合代理的批量抓取)
Query query = session.createQuery("from Classes where id in(1,2,3)");
List<Classes> list = query.list();
for(Classes c : list){
for(Iterator i = c.getStudents().iterator();i.hasNext();){
Student s = (Student) i.next();
System.out.println("姓名:"+s.getName()+"班级:"+c.getClassName());
}
}
(一)保持默认,同fetch="select",如:
<set name="students" inverse="true" cascade="all" fetch="select">
fetch="select",另外发送一条select语句抓取当前对象关联实体或集合
(二)设置fetch="join",如:
<set name="students" inverse="true" cascade="all" fetch="join">
fetch="join",hibernate会通过select语句使用外连接来加载其关联实体或集合
此时lazy会失效
(三)设置fetch="subselect",如:
<set name="students" inverse="true" cascade="all" fetch="subselect">
fetch="subselect",另外发送一条select语句抓取在前面查询到的所有实体对象的关联集合
批量抓取
batch-szie可以在类级别的批量加载实体
<class name="Classes" table="t_classes" batch-size="3"> 类上的批量抓取 from Student s where id in(1,11,21)
batch-szie可以在集合级别的批量加载实体
<set name="students" inverse="true" cascade="all" batch-size="5"> 集合上的批量抓取 from Classes c where id in(1,2,3)
类/实体级别的批量抓取很容易理解。假设你在运行时将需要面对下面的问题:你在一个Session中载入了25个Cat实例,每个Cat实例都拥有一个引用成员owner,其指向Person,而Person类是代理,同时lazy="true"。 如果你必须遍历整个cats集合,对每个元素调用getOwner()方法,Hibernate将会默认的执行25次SELECT查询, 得到其owner的代理对象。这时,你可以通过在映射文件的Person属性,显式声明batch-size,改变其行为:
<class name="Person" batch-size="10">...</class>
随之,Hibernate将只需要执行三次查询,分别为10、10、 5。
你也可以在集合级别定义批量抓取。例如,如果每个Person都拥有一个延迟载入的Cats集合, 现在,Sesssion中载入了10个person对象,遍历person集合将会引起10次SELECT查询, 每次查询都会调用getCats()方法。如果你在Person的映射定义部分,允许对cats批量抓取, 那么,Hibernate将可以预先抓取整个集合。请看例子:
<class name="Person">
<set name="cats" batch-size="3">
...
</set>
</class>
如果整个的batch-size是3,那么Hibernate将会分四次执行SELECT查询, 按照3、3、3、1的大小分别载入数据。
这里的每次载入的数据量还具体依赖于当前Session中未实例化集合的个数。
延迟抓取要注意的问题。在一个打开的Hibernate session上下文之外调用延迟集合会导致一次意外。
比如:
s = sessions.openSession();
Transaction tx = s.beginTransaction();
User u = (User) s.createQuery("from User u where u.name=:userName").setString("userName", userName).uniqueResult();
Map permissions = u.getPermissions();
tx.commit();
s.close();
Integer accessLevel = (Integer) permissions.get("accounts"); // Error!
在Session关闭后,permessions集合将是未实例化的、不再可用,因此无法正常载入其状态。
Hibernate对脱管对象不支持延迟实例化. 这里的修改方法是:将permissions读取数据的代码 移到tx.commit()之前。
通常情况下,我们并不使用映射文档进行抓取策略的定制。更多的是,保持其默认值,然后在特定的事务中,使用HQL的左连接抓取(left join fetch) 对其进行重载。这将通知 Hibernate在第一次查询中使用外部关联(outer join),直接得到其关联数据。
在条件查询 API中,应该调用 setFetchMode(FetchMode.JOIN)语句。
例如:
User user = (User) session.createCriteria(User.class)
.setFetchMode("permissions", FetchMode.JOIN)
.add( Restrictions.idEq(userId) )
.uniqueResult();
截然不同的一种避免N+1次查询的方法是,使用二级缓存
使用子查询抓取(Using subselect fetching)
假若一个延迟集合或单值代理需要抓取,Hibernate会使用一个subselect重新运行原来的查询,一次性读入所有的实例。这和批量抓取的实现方法是一样的,不会有破碎的加载。
关联(查询引用实体),批量(类批量抓取,集合批量抓取)