避免n + 1渴望获取子集合元素关联

时间:2012-11-14 12:15:12

标签: java hibernate jpa hql

我有以下课程:

@Entity
@Table(name = "base")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "DISCRIMINATOR", discriminatorType = DiscriminatorType.STRING)
@ForceDiscriminator
public class Base {
    // ...
}

@Entity
@DiscriminatorValue("foo")
public class Foo extends Base {
    @OneToMany( mappedBy = "foo", cascade=CascadeType.ALL )
    private List<Bar> bars = new ArrayList<Bar>();

    // ...
}

@Entity
public class Bar {
    @ManyToOne (optional = false)
    @JoinColumn(name = "foo_id" )
    private Foo foo;

    @OneToOne
    @JoinColumn(name = "baz_id", nullable = false)
    private Baz baz;

    //...
}

@Entity
public class Baz {
    // ...
}

现在我基本上想要加载所有Base,但是在适用的情况下需要加载条,因此我使用以下查询:

SELECT b FROM Base b LEFT JOIN FETCH b.bars

虽然这有效,但似乎为Bar实体生成了SELECT N + 1问题:

Hibernate: /* SELECT b FROM Base b LEFT JOIN FETCH b.bars */ SELECT ...
Hibernate: /* load com.company.domain.Baz */ SELECT ...
Hibernate: /* load com.company.domain.Baz */ SELECT ...

是否有可能告诉hibernate急切地为子集合中的每个元素加载一个关联而不依赖于N + 1 SELECT?

我尝试了以下查询的内容,这显然不起作用,因为它是一个集合:

SELECT b FROM Base b LEFT JOIN FETCH b.bars LEFT JOIN FETCH b.bars.baz
//Results in: illegal attempt to dereference collection [Foo.id.bars] with element property reference [baz]

我也尝试过使用IN(b.bars) bars,虽然这允许我引用子集合,但它似乎并不急于加载我的目标吧集合。

解释为什么会发生这种情况也很好,因为我似乎无法弄明白。

3 个答案:

答案 0 :(得分:5)

如果你想用out(n + 1)选择检索Bar和Baz,请使用以下hql。

SELECT b FROM Base b LEFT JOIN FETCH b.bars bar LEFT JOIN FETCH bar.baz

这应该只产生一个sql。

另外,如果您不想获取'Baz',只需从Bar-&gt; Baz'懒惰'进行关联。

JPA默认强制'eager'获取'@OneToOne'和'@ManyToOne'关联。所以,你必须明确地使它变得懒惰,如下所示。

@Entity
public class Bar {

    @OneToOne
    @JoinColumn(name = "baz_id", nullable = false, fetch=FetchType.Lazy)
    private Baz baz;

    //...
}

答案 1 :(得分:0)

我的方法(我有一个有限的,稳定数量的二级实体)

首先,让所有酒吧进入会话:

 SELECT bar FROM Bar bar

之后,所有Bar实体都将位于缓存中,您的查询将无需其他实体即可访问它们。

答案 2 :(得分:0)

我想说在这种情况下,获取策略的改变可能会有所帮助。文档说:

http://docs.jboss.org/hibernate/orm/4.1/manual/en-US/html_single/#performance-fetching-batch

提取:

  

您还可以启用批量提取集合。例如,如果   每个人都有一个懒惰的猫集合,10人   目前在Session中加载,遍历所有人都会   生成10个SELECT,每次调用getCats()一个。如果启用   在Person的映射中批量获取cats集合,   Hibernate可以预取集合:

<class name="Person">
    <set name="cats" batch-size="3">
        ...
    </set>
</class>
  

批量大小为3时,Hibernate将加载3个,3个,3个,1个集合   四个SELECT。同样,属性的值取决于   预期的特定会话中未初始化的集合数。

我正在使用它并且效果很好。如果我正在分页并且总是只选择20条记录,则批量大小=“20”的效果很好。如果我需要20以上,那么对DB的调用仍会减少