连接的高效Hibernate标准返回许多部分重复

时间:2016-04-19 21:23:28

标签: java hibernate optimization criteria hibernate-criteria

我正在获取一长串实体,这些实体引用其他引用的实体......最后,通常所有实体都引用单个user作为owner。并不奇怪,因为所查询的是属于单个user的实体。许多行中有更多的部分重复;实际上,只有一小部分是唯一数据。由于查询似乎很慢,我可以通过使用

分别获取内容来获得一些收益
criteria.setFetchMode(path, FetchMode.SELECT);

这适用于我的上述情况,但是当查询许多用户(作为管理员)时,它变得非常糟糕,因为hibernate为每个 user发出单独的查询,而不是像

SELECT * FROM User WHERE id IN (?, ?, ..., ?)

根本不提取它们(每个实体不能比一个查询更糟糕)。我想知道我错过了什么?

因此,我没有获取大量冗余数据,而是遇到了1 + N问题,显然会有1 + 1个查询。

  • 有没有办法指示Hibernate使用正确的查询?
  • 有没有办法阻止Hibernate通过在条件本身中指定它来获取所有者(而不是将fetch=FetchType.LAZY放在字段上;懒惰应该是查询特定的?)

我认为这不重要,但我的课程就像

class Child {
    @ManyToOne Father father;
    @ManyToOne Mother mother;
    ...
}
class Father {
    @ManyToOne User owner;
    ...
}
class Mother {
    @ManyToOne User owner;
    ...
}

,查询就像

createCriteria(Child.class)
.add(Restrictions.in("id", idList))
.add(Restrictions.eq("isDeleted", false))

.createAlias("Father", "f")
.add(Restrictions.eq("f.isDeleted", false))
.setFetchMode("f.owner", FetchMode.SELECT)

.createAlias("Mother", "m")
.add(Restrictions.eq("m.isDeleted", false))
.setFetchMode("m.owner", FetchMode.SELECT)

.list();

重要的是owner没有被使用并且可以被代理。 FetchMode.SELECT的javadoc说

  

使用单独的选择

急切地获取

所以它基本上承诺我想要的1 + 1查询,而不是“每个实体使用单独的选择”。

4 个答案:

答案 0 :(得分:3)

Fetch profiles旨在帮助您实现您想要的目标,但目前非常有限,您只能使用连接样式的获取配置文件覆盖默认的获取计划/策略(您可以做一个懒惰协会渴望,但反之亦然)。但是,您可以使用此trick来反转该行为:默认情况下使关联变为惰性,并默认为所有会话/事务启用配置文件。然后禁用您希望延迟加载的交易中的个人资料。

恕我直言,上面的解决方案看起来过于繁琐,我在大多数用例中使用的方法,以避免加载冗余数据和N + 1选择问题是使关联变得懒惰并定义batch size

答案 1 :(得分:1)

我写了一个小项目来证明这种行为。根据您的条件生成的SQL如下:

select
    this_.id as id1_0_4_,
    this_.father_id as father_i3_0_4_,
    this_.isDeleted as isDelete2_0_4_,
    this_.mother_id as mother_i4_0_4_,
    f1_.id as id1_1_0_,
    f1_.isDeleted as isDelete2_1_0_,
    f1_.owner_id as owner_id3_1_0_,
    user5_.id as id1_3_1_,
    user5_.isDeleted as isDelete2_3_1_,
    m2_.id as id1_2_2_,
    m2_.isDeleted as isDelete2_2_2_,
    m2_.owner_id as owner_id3_2_2_,
    user7_.id as id1_3_3_,
    user7_.isDeleted as isDelete2_3_3_ 
from
    Child this_ 
inner join
    Father f1_ 
        on this_.father_id=f1_.id 
left outer join
    User user5_ 
        on f1_.owner_id=user5_.id 
inner join
    Mother m2_ 
        on this_.mother_id=m2_.id 
left outer join
    User user7_ 
        on m2_.owner_id=user7_.id 
where
    this_.id in (
        ?, ?
    ) 
    and this_.isDeleted=? 
    and f1_.isDeleted=? 
    and m2_.isDeleted=?
  1. 更改条件API中的FetchMode不会影响查询。仍然查询所有者数据。
  2. Ids位于" in"子句和Hibernate没有为每个Id发出单独的查询。
  3. 如其他答案中所述,如果实体关系设置为EAGER,即JPA默认值,则您无法更改Criteria API中的提取模式。需要将获取模式更改为LAZY

    你可以看到它here

答案 2 :(得分:1)

总结一下我的挫败感......在这方面,Hibernate充满了惊喜(错误?):

  • 除非使用@ManyToOne(fetch=FetchType.LAZY)声明属性,否则无法更改任何内容
  • 默认为FetchType.EAGER,这是愚蠢的,因为无法覆盖
  • 使用criteria.setFetchMode(path, FetchMode.SELECT)是毫无意义的,因为它总是一个无操作(要么被忽略,因为属性不可覆盖,或者属性已经很懒)!
  • 默认情况下,懒惰地获取1 + N问题
  • 可以通过班级@BatchSize注释
  • 进行控制
  • 在标量字段上放置@BatchSize注释会被默默忽略

为了获得我想要的东西(两个SQL查询),我只需要两件事:

  • 使用@ManyToOne(fetch=FetchType.LAZY)
  • 声明属性
  • @BatchSize(size=aLot)放置在属性类

这很简单,但有点难以找到(因为上面所有被忽略的事情)。我还没有查看过fetch配置文件。

答案 3 :(得分:1)

  

除非声明属性   @ManyToOne(fetch=FetchType.LAZY),你无法改变任何事情

是的,至少在目前情况下,直到获取配置文件功能被扩展为提供将预先加载更改为延迟的能力。

  

默认为FetchType.EAGER,这是愚蠢的,因为它不可能   覆盖

是的,我同意它很糟糕,但在Hibernate本机API中,默认情况下一切都是懒惰的;除非另有明确规定,否则JPA要求一个协会渴望。

  

使用criteria.setFetchMode(path,FetchMode.SELECT)毫无意义   它总是一个无操作(要么被忽略,因为它   不可覆盖的财产或财产的热情是懒惰的   已)!

有了它,你应该能够覆盖其他懒惰的提取模式。请参阅主要Hibernate贡献者之一关于javadoc混淆的HHH-980this comment

  

默认情况下,懒惰地获取1 + N问题

它与延迟加载无关,如果你没有在同一个查询中获取急切加载的关联,它也是预先加载的默认值。

  

可以通过类级@BatchSize注释

来控制它

你必须将它放在类级别才能使它与该实体的一个关联生效; this answer很有帮助。对于集合关联(与其他实体中定义的实体的多对多关联),您可以灵活地为每个关联单独定义它。