hibernate 抓取策略(Fetching strategies)
抓取策略(fetching strategy) 是指:当应用程序需要在(Hibernate实体对象图的)关联关系间进行导航的时候, Hibernate如何获取关联对象的策略。抓取策略可以在O/R映射的元数据中声明,也可以在特定的HQL 或条件查询(Criteria Query)中重载声明。
Hibernate3 定义了如下几种抓取策略:
连接抓取(Join fetching) - Hibernate通过 在SELECT语句使用OUTER JOIN(外连接)来 获得对象的关联实例或者关联集合。
查询抓取(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,属性延迟加载 - 对属性或返回单值的关联而言,当其实例变量被访问的时候进行抓取。需要编译期字节码强化,因此这一方法很少是必要的。
这里有两个正交的概念:关联何时被抓取,以及被如何抓取(会采用什么样的SQL语句)。不要混淆它们!我们使用抓取来改善性能。我们使用延迟来定义一些契约,对某特定类的某个脱管的实例,知道有哪些数据是可以使用的。
----------------------------------------------------
---------------------------
一个抓取的例子:
例子:ForumGroup和Forum
- public class ForumGroup {
- @OneToMany(fetch=FetchType.EAGER,mappedBy="group")
- private List<Forum> forums = new ArrayList<Forum>();
- }
- public class Forum {
- @ManyToOne(fetch=FetchType.LAZY)
- @JoinColumn(name="groupId",nullable=false)
- @org.hibernate.annotations.ForeignKey(name="fk_Forum_groupId_ForumGroup")
- private ForumGroup group;
- }
由于ForumGroup的forums被配制为eager fetch,也就是说在加载ForumGroup时,会同一并把它的所有Forum一起加载出来,这个过程是通过left out join来实现的。下面是hibernate打出的sql.
select this_.id as id3_1_, this_.creationTime as creation2_3_1_, this_.description as descript3_3_1_, this_.modifiedTime as modified4_3_1_, this_.name as name3_1_, forums2_.groupId as groupId3_, forums2_.id as id3_, forums2_.id as id2_0_, forums2_.creationTime as creation2_2_0_, forums2_.description as descript3_2_0_, forums2_.groupId as groupId2_0_, forums2_.modifiedTime as modified4_2_0_, forums2_.name as name2_0_ from ForumGroup this_ left outer join Forum forums2_ on this_.id=forums2_.groupId
假定有1个ForumGroup,它有3个Forum,上面的sql会生成3条记录。那么Hibernate是如何封装这3条数据的呢?从实际的代码运行结果中可以看到:首先,Hibernate确实只生成了一个ForumGroup的实例和3个Forum的实例。但是如果你使用loadAllForumGroup时,会返回3个ForumGroup的引用,指向同一个ForumGroup的实例!
如果把ForumGroup的forums被配制为lazy fetch,会解决这个问题。但这也只是回避了这个问题!
正确的解决方法是什么呢?
如果想过滤join和fetch中的重复对象,有两种方法:
1.将续集里封装成set.比如:Set noDupes = new LinkedHashSet(resultList))
2.使用DISTINCT。比如:select distinct i from Item i join fetch i.bids
另外一个问题是:
在用JPA进行注释时,如果一个实体里要映射多个集合实体时,我们不能把两个集合的的FetchType设置为EAGER,此时只能设置为LAZY,否则会报:cannot simultaneously fetch multiple bags。或者我们也可以借助:@IndexColumn (加了它就不再是bag集合了,而是list集合了)。也就是说,hibernate不允许一次抓取多个bag.