【问题标题】:Avoiding n+1 eager fetching of child collection element association避免 n+1 急切获取子集合元素关联
【发布时间】:2012-11-02 22:32:33
【问题描述】:

我有以下课程:

@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 SELECTs?

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

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,虽然这允许我引用子集合,但它似乎并没有急切地加载 bar 集合,这是我的目标。

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

【问题讨论】:

  • 您是否遇到 Baz 或 Bar 的“n+1 选择”问题?在您提供的堆栈跟踪跟踪中,看起来 n+1 选择是针对“Baz”的。
  • 没错。正如预期的那样,Bar 实体的集合已正确加载,但这种加载显然会强制选择 N 个 Baz 元素。

标签: java hibernate jpa hql


【解决方案1】:

如果您想检索没有 (n+1) 个选择的 Bar 和 Baz,请使用以下 hql。

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

这应该只产生一个 sql。

另外,如果您不想获取 'Baz',只需从 Bar->Baz 'lazy' 进行关联。

默认情况下,JPA 对“@OneToOne”和“@ManyToOne”关联强制执行“eager”获取。所以,你必须明确地让它变得懒惰,如下所示。

@Entity
public class Bar {

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

    //...
}

【讨论】:

  • 有效!不敢相信我没有弄清楚(也就是说,我以为我已经尝试过了,但显然没有).. 不知道它会强制为这些关联加载。感谢您的提醒。
【解决方案2】:

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

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 个集合 四个选择。同样,属性的值取决于 特定 Session 中未初始化集合的预期数量。

我正在使用它并且效果很好。如果我在分页并且总是只选择 20 条记录,那么 batch-size="20" 效果很好。如果我需要超过 20 个,对 DB 的调用仍然会减少

【讨论】:

  • 我理解你的建议,但问题不在于集合的加载,它工作正常。问题是,在急切地加载集合时,hibernate 会急切地以 SELECT N+1 方式获取集合中每个元素的关联(我从未接触过关联)。
  • 我明白了。我终于明白了这个问题。好的,但即使是类也支持 batch-size="N"。这对 Baz 类没有帮助吗?请检查这个(向下滚动一点)docs.jboss.org/hibernate/orm/4.1/manual/en-US/html_single/…
【解决方案3】:

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

首先,让所有 Bars 进入会话:

 SELECT bar FROM Bar bar

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

【讨论】:

  • 我明白了,当 Baz 实体的数量有限时,这似乎是一种不错的方法。但是,就我而言,这不是一个可行的解决方案,因为 Baz 实体的数量 > 150。
  • 这不是那么大,我的实体是 180。在开发设置中调用使用这种方法两次并且需要更多逻辑的页面需要少于 250 毫秒才能完成(调用旧方法需要更多超过 1000 秒的方式)。
  • 我明白了,我的问题是数量不是固定的,所以随着更多数据在生产设置中添加到应用程序中,将创建更多这种类型的实体。
  • 没想到。不过,这里似乎对我没有多大帮助,因为它似乎取而代之的是很多其他属性(示例中未显示)。通常,可以对域模型进行更多考虑。映射,但遗憾的是它不容易改变..
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-02-19
  • 2023-03-24
  • 2021-02-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多