Hibernate抓取策略

来源:互联网 发布:ubuntu下移动文件夹 编辑:程序博客网 时间:2024/06/05 20:02

1. 抓取策略(Fetching strategies)

抓取策略(fetching strategy) 是指:当应用程序需要在(Hibernate实体对象图的)关联关系间进行导航的时候, Hibernate如何获取关联对象的策略。

抓取策略可以在O/R映射的元数据中声明,通过关联关系上面设置属性 fetch="select/join"

也可以在特定的HQL , 通过(left)join fetch 关键字

条件查询(Criteria Query)中重载声明, 通过setFetchMode方法。


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

  • 连接抓取(Join fetching) - Hibernate通过 在SELECT语句使用OUTER JOIN(外连接)来 获得对象的关联实例或者关联集合。(如果使用了fetch="join"的设置, 那么lazy="true"或者lazy="proxy"的设置就不起作用)

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

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

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

Hibernate会区分下列各种情况:

  • Immediate fetching,立即抓取 - 当宿主被加载时,关联、集合或属性被立即抓取。

  • Lazy collection fetching,延迟集合抓取- 直到应用程序对集合进行了一次操作时,集合才被抓取。(对集合而言这是默认行为。)

  • "Extra-lazy" collection fetching,"Extra-lazy"集合抓取 -对集合类中的每个元素而言,都是直到需要时才去访问数据库。除非绝对必要,Hibernate不会试图去把整个集合都抓取到内存里来(适用于非常大的集合)。

  • Proxy fetching,代理抓取 - 对返回单值的关联而言,当其某个方法被调用,而非对其关键字进行get操作时才抓取。

  • "No-proxy" fetching,非代理抓取 - 对返回单值的关联而言,当实例变量被访问的时候进行抓取。与上面的代理抓取相比,这种方法没有那么“延迟”得厉害(就算只访问标识符,也会导致关联抓取)但是更加透明,因为对应用程序来说,不再看到proxy。这种方法需要在编译期间进行字节码增强操作,因此很少需要用到。

  • Lazy attribute fetching,属性延迟加载 - 对属性或返回单值的关联而言,当其实例变量被访问的时候进行抓取。需要编译期字节码强化,因此这一方法很少是必要的。

这里有两个正交的概念:关联何时被抓取 (lazy属性),以及被如何抓取(fetch属性,会采用什么样的SQL语句)。不要混淆它们!我们使用抓取来改善性能。我们使用延迟来定义一些契约,对某特定类的某个脱管的实例,知道有哪些数据是可以使用的。

1.1. 操作延迟加载的关联

默认情况下,Hibernate 3对集合使用延迟select抓取 (lazy="true", fetch="select"),对返回单值的关联使用延迟代理抓取(lazy="proxy", fetch="select")。对几乎是所有的应用而言,其绝大多数的关联,这种策略都是有效的。

注:经过我的验证:

1)    对于one-to-many映射 默认情况下, lazy属性为true。

         对于many-to-one, 默认情况下, lazy属性为proxy;

        对于one-to-one, 因为有contrained 属性的影响, Hibernate总是采取预先抓取, 而不管lazy属性为何值, 如果 contrained="true", Hibernate采取 select的抓取策略;如果contrained="false", Hibernate采用left-outer-join的抓取方式。

2)  .hbm定义如下

<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE hibernate-mapping PUBLIC'-//Hibernate/Hibernate Mapping DTD 3.0//EN''http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd'><hibernate-mapping><class name="cn.javass.h4.dept.DeptModel" table="tbl_dept"><id name="id"><generator class="native" /></id><property name="name"></property><one-to-one name="du" property-ref="dept" constrained="true" cascade="save-update,delete"></one-to-one><set name="subDepts" inverse="true"  cascade="save-update,delete"><key column="deptId"/><one-to-many class="cn.javass.h4.dept.SubDeptModel"></one-to-many></set></class><class name="cn.javass.h4.dept.DeptUserModel" table="tbl_user"> <id name="uuid"><generator class="native" /></id><property name="userId"></property><property name="name"></property><property name="age"></property><many-to-one name="dept" column="deptId" class="cn.javass.h4.dept.DeptModel"   cascade="save-update,delete" ></many-to-one></class><class name="cn.javass.h4.dept.SubDeptModel" table="tbl_subdept"> <id name="id"><generator class="native" /></id><property name="name"></property><many-to-one name="dept" column="deptId" class="cn.javass.h4.dept.DeptModel"  cascade="save-update,delete" ></many-to-one></class></hibernate-mapping>

对于DeptModel和DeptUserModel来说, 他们之间是一对一双向关联:
 session.get (DeptModel.class, id) 会预先抓取DeptModel数据和DeptUserModel数据(无论lazy属性为何值), 并填充DeptModel.du 为预先抓取的数据, 而且DeptModel.du.dept也将填充DeptModel对象本身。
session.get(DeptUserModel.class, id)的行为根据lazy来定, 如果"lazy=“proxy”, 则会延迟加载DeptModel数据; 如果"lazy=false",则会同时预先抓取DeptModel数据, 并填充DeptUserModel.dept为抓取的DeptModel数据, 同时, DeptUserModel.dept.du也将填充DeptUserModel本身。
对于DeptModel和SubDeptModel来说, 他们之间是一对多双向关联:
session.get (DeptModel.class, id)的行为根据one-to-many 的lazy来定,如果"lazy=“true”, 则会延迟加载SubDeptModel数据; 如果"lazy=false",则会同时预先抓取SubDeptModel数据, 并填充DeptModel.subDepts为抓取的SubDeptModel数据集合, 同时,DeptModel.subDepts.dept 也将填充DeptModel本身。
session.get (SubDeptModel.class, id)的行为根据many-to-one 的lazy来定,如果"lazy=“true”, 则会延迟加载DeptModel数据; 如果"lazy=false",则会同时预先抓取DeptModel数据, 并填充SubDeptModel.dept为抓取的DeptModel数据,不过, SubDeptMode.dept.subDepts仍然为代理对象(因为subDepts默认懒加载)。

从上面的分析可以看出, 在Hibernate中,对于返回 集合的关联,他的集合对象的数据抓取和设置只能通过设置一方Set(one-to-many)的属性 lazy=false或者HQL中的join fetch等方式来实现, 多方many-to-one 上面设置的lazy="false"属性并不能让一方的集合对象预先抓取和设置 。
对于任何返回单值的关联而言, 只要任何一方的lazy="false"被设置了(或者通过join等方式抓取了关联方的数据), 该关联对象总会被预先抓取的数据正确的设置。(因为任何一方的lazy="false"必将导致关联双方的数据被同时抓取, 所以任何单值关联的数据都可以得到正确的设置)。
这个结论具有普遍意义, 无论对于load/get, HQL还是Criteria中的查询都成立。

注意:假若你设置了hibernate.default_batch_fetch_size,Hibernate会对延迟加载采取批量抓取优化措施(这种优化也可能会在更细化的级别打开)。

然而,你必须了解延迟抓取带来的一个问题。在一个打开的Hibernate session上下文之外调用延迟集合会导致一次意外。比如:

<span style="FONT-SIZE: 14px"><strong>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!</strong></span>

Session关闭后,permessions集合将是未实例化的、不再可用,因此无法正常载入其状态。Hibernate对脱管对象不支持延迟实例化. 这里的修改方法是:将permissions读取数据的代码 移到tx.commit()之前。

除此之外,通过对关联映射指定lazy="false",我们也可以使用非延迟的集合或关联。但是, 对绝大部分集合来说,更推荐使用延迟方式抓取数据。如果在你的对象模型中定义了太多的非延迟关联,Hibernate最终几乎需要在每个事务中载入整个数据库到内存中!

但是,另一方面,在一些特殊的事务中,我们也经常需要使用到连接抓取(它本身上就是非延迟的),以代替查询抓取。 下面我们将会很快明白如何具体的定制Hibernate中的抓取策略。在Hibernate3中,具体选择哪种抓取策略的机制是和选择 单值关联或集合关联相一致的。

1.2. 调整抓取策略(Tuning fetch strategies)

查询抓取(默认的)在N+1查询的情况下是极其脆弱的,因此我们可能会要求在映射文档中定义使用连接抓取:

<set name="permissions"             fetch="join">    <key column="userId"/>    <one-to-many class="Permission"/></set>
<many-to-one name="mother" class="Cat" fetch="join"/>

在映射文档中定义的抓取策略将会对以下列表条目产生影响:

  • 通过get()load()方法取得数据。

  • 只有在关联之间进行导航时,才会隐式的取得数据。

  • 条件查询

  • 使用了subselect抓取的HQL查询


不管你使用哪种抓取策略,定义为非延迟的类图会被保证一定装载入内存。注意这可能意味着在一条HQL查询后紧跟着一系列的查询。

通常情况下,我们并不使用映射文档进行抓取策略的定制。更多的是,保持其默认值,然后在特定的事务中, 使用HQL的左连接抓取(left join fetch) 对其进行重载。这将通知 Hibernate在第一次查询中使用外部关联(outer join),直接得到其关联数据。 在条件查询 API中,应该调用setFetchMode(associationPath, FetchMode.JOIN)语句。

也许你喜欢仅仅通过条件查询,就可以改变get()load()语句中的数据抓取策略。例如:

User user = (User) session.createCriteria(User.class)            .setFetchMode("permissions", FetchMode.JOIN)            .add( Restrictions.idEq(userId) )            .uniqueResult();

(这就是其他ORM解决方案的“抓取计划(fetch plan)”在Hibernate中的等价物。)

截然不同的一种避免N+1次查询的方法是,使用二级缓存。  


1.3 HQL中left join 和 left join fetch的区别

假定有 DeptModel, DeptUserModel, SubDeptModel三个对象, 其中 DeptModel 和DeptUserModel是一对一关系, DeptModel和SubDeptModel是一对多关系, DeptModel是“一”方。 (具体的细节可以参考上面的.hbm文件)

一对多关系:

        HQL:  from DeptModel d left join d.subDepts s where d.id=? and s.id in (?, ?, ?)

这个语句将返回一个元素为 object[2]数组的list, 其中object[0]为DeptModel对象, object[1]为一个SubDeptModel对象,因为DeptModel和SubDeptModel是一对多的关系, 所以返回结果是由多个object[2]元素组成的list。另外, DeptModel.subDepts 集合对象默认策略是懒加载的, 所以对于object[0]中德DeptModel, 他的 DeptModel.subDepts 返回的是一个代理对象,数据没有抓取(虽然object[1]中德SubDeptModel对象数据已经抓取, 但是他并没有填充到DeptModel.subDepts中) 。

        HQL: from DeptModel d left join fetch d.subDepts s where d.id=? and s.id in (?, ?, ?)

由于使用了fetch关键字 ,这个语句将返回一个元素为 DeptModel 的list,并且DeptModel.subDepts将使用 根据where条件 (d.id=? and s.id in (?, ?, ?) )抓取到的SubDeptModel对象集合进行填充。(注意这里subDepts是根据条件填充集合项, 而不是像session.load(DeptModel.class, id)那样, 填充deptId为id的所有SubDeptModel对象集合)

另外, 由于使用了fetch, 上面的HQL返回的是DeptModel对象list, 并且list当中的DeptModel对象是重复的,这个时候可以通过select distinct 或者 使用java set的方式消除重复值。 (重复值产生的原因应该是 HQL 原先返回的是 数据项为object[2]数组的list,由于一对多的原因, 原先object[0]里面放的都是相同的DeptModel, object[1]放的是不同的SubDeptModel对象,而使用fetch以后, object[1]的对象被砍掉了,Hibernate只返回Object[0]里面的DeptModel对象, 所以造成了重复值的现象。 )

重复值的这种现象在Criteria中也是存在的, 因为Criteria默认情况下都是返回单个的实体, 比如下面的Criteria返回的是DeptModel对象的list, 由于一个DeptModel对应多个SubDeptModel对象, 所以, list中存在的是多个相同的DeptModel对象。

Criteria c1  = s.createCriteria(DeptModel.class, "d").createAlias("d.subDepts", "sd").add(Restrictions.eq("id", id));

List l = c1.list();

补充:

HQL : from DeptModel d left join d.du left join fetch d.subDepts sd where d.id=?

这条语句将返回一个 数据项为object[2]的list, 其中, object[0]为DeptModel对象, object[1]为DeptUserModel对象, 并且object[0]中的DeptModel对象的subDepts已经使用预先抓取的数据填充。


一对一关系:

HQL: from DeptUserModel u left join u.dept where u.uuid=?

这个语句将返回一个元素为 object[2]数组的list, 其中object[0]为DeptUserModel对象, object[1]为一个DeptModel对象,因为DeptUserModel和DeptModel是一对一的关系, 所以返回结果是只有一个object[2]元素组成的list。另外, DeptUserModel.dept对象的数据预先抓取了(不管该关联lazy属性为何值,因为这是一个单值关联并且DeptModel数据已通过join抓取,所以可以正确的设置dept), 这是和上面一对多关系中的不同之处。

HQL: from DeptUserModel u left join fetch u.dept where u.uuid=?

        由于使用了fetch关键字 , 这个语句将返回一个元素为 DeptUserModel 的list (实际上list里面只有一个DeptUserModel), 并且 DeptModel.dept对象的数据预先抓取了 这个行为和上面的一对多关系是一致的。


1.4 关于setFetchMode: 

FetchMode是让两个表建立连接,仅此而已。
例如:一个Emp只能属于一个dept,而一个dept中可以有好多Emp,
这时在Emp中就有一个dept对象 


public class Emp{

.......
private dept d;
get()方法
set()方法
......

public class Dept {

...

private Set emps;

...

}

SetFetchMode方法和HQL中的fetch关键字类似,如果我想取出emp的时候顺便把它的dept相关的东西给取出来,此时我就需要setFetchMode("dept",FetchMode.JOIN),这样,我就可以正好把相关的dept里面的数据也取出来了,如果打印数据库执行语句,此时是生成的左连接,
但是注意:setFetchMode:仅仅能取出dept的数据,setFetchMode不能完成查询功能。如果查询条件里面有dept里面的东西,那setFetchMode就完不成相应的查询,如果想完成查询,你就需要用createAlias了(createAlias主要是用于查询,建立两表之间的关系,查询与两个表相关的数据。 )

例如,我们想查询dept为“人力资源部”的所有员工, 并且员工的dept属性被预先抓取:
c = session.createCriteria(Emp.class); 

c.setFetchMode("dept",FetchMode.JOIN); 

c.add(Restrictions.eq("dept.deptName","人力资源部"));
如果只是这两句话的话,肯定有错误的,需要使用到createAlias。

那么上面的查询这样写就可以了:
c.setFetchMode("dept",FetchMode.JOIN);
c.createAlias("dept","dept").add(Restrictions.eq("dept.deptName","人力资源部"));
这样就可以找出符合要求的结果。

注:根据我的实验结果  (Hibernate 3.6)

1) 对于上面的例子, setFetchMode("dept",FetchMode.JOIN) 完全可以省略, 因为emp.dept是一个单值关联, Hibernate在通过createAlias join查询到 dept数据的时候会自动填充这个单值关联(原因见HQL join fetch 那里的说明)。

2)对于多值关联, 比如Department里面的Emp 对象集合, setFetchMode如果和createAlias一起用的话, 不起作用,比如下面这句: 

session.createCriteria(Dept.class).createAlias("emps", "emps").setFetchMode("emps", FetchMode.JOIN) 

FetchMode.JOIN不起作用,Dept.emps 属性并不会得到数据的设置, 只是返回一个代理对象,感觉是有冲突或者是Hibernate的bug。

     这种情况下, 只有setFetchMode单独使用才有效果:

session.createCriteria(Dept.class).setFetchMode("emps",FetchMode.JOIN) ; 

      这个语句会通过left out join 把emps数据预先抓取并填充到Dept.emps中。

3) session.createCriteria(Dept.class).createAlias("emps", "emps").list();

      这个语句会返回一个Dept对象组成的list, 其中, Dept对象会有重复(原因见join fetch那里的说明),并且, Dept.emps对象没有被预先抓取(延迟加载, 只有通过setFetchMode才行)

参考:

http://blog.csdn.net/ailiandeziwei/article/details/8465914

http://www.cnblogs.com/rongxh7/archive/2010/05/12/1733088.html

0 0
原创粉丝点击