防止 JPA 中的 N+1 查询问题

7
我有一个JPA实体Order与Customer的ManyToOne关系。它是双向的,因此Customer也有一个OneToMany字段orders。这两个关系都使用EAGER获取(或者在OpenJPA fetchplan中)。
当我从Order中选择时,我得到1个select用于订单,以及N个select用于Customer.orders字段。让我惊讶的是,即使我使用JOIN FETCH(在单向情况下确实有效),这个问题在OpenJPA、EclipseLink和Hibernate中都存在。
有没有好办法来解决这个问题?是否有任何解决更复杂图形的N+1 select问题的解决方案?
编辑: 我自己研究的结果: - 对于我正在使用的OpenJPA,我还不知道解决方法 - 对于Hibernate @Fetch(FetchMode.SUBSELECT)可以解决问题。使用@BatchSize也有帮助,它会同时选择给定数量的customer.orders字段。 - 对于EclipseLink,我找到了类似的特性@BatchFetch(value=BatchFetchType.IN),但在这种情况下它没有帮助,我认为它不能在双向关系中高效地处理这个问题。

这有点啰嗦的问题。你是想帮忙解决一个具体的问题,还是只是想抱怨JPA? - millimoose
你说得对,我对JPA有点失望,我会编辑我的问题让它更加明确。 - Henno Vermeulen
1
你真的需要在Customer.orders上使用EAGER加载吗? - Esteve
@Esteve,你说得很有道理。当制作一个简单的订单及其客户列表时,你实际上不需要这些客户的其他订单!但是,在其他情况下可能需要加载这些字段,我认为可以在不进行N+1查询的情况下选择它们。 - Henno Vermeulen
3个回答

1

3
谢谢。使用原生查询绝对有帮助。但这感觉就像你必须手写本应由ORM执行的内容。这可能会导致很多手动操作和维护上的噩梦。 - Henno Vermeulen
@SlowStrider 这个想法是你不应该经常这样做。N+1懒加载通常是大多数问题的正确方式。此外,当涉及到性能可靠优化时,你会发现SQL(而不是JPA HQL)是你唯一的选择...所以除非必要,否则不要这样做。 - Adam Gent
对于投反对票的人,如果您能告诉我为什么会很有帮助。N+1查询问题不能神奇地解决。要么您拥有一个字段的笛卡尔积,要么您延迟加载每个对象。我不确定人们期望什么。没有一个该死的ORM可以预测数据库性能并随机决定何时执行渴望(笛卡尔积)或者懒惰(N+1)。我不确定人们期望什么。对不起,我不能只提供一段代码的剪切和粘贴来解决这个问题。 - Adam Gent
我认为被踩的原因是你说N+1懒加载通常是正确的方式。我不同意这个观点...实际上,我认为JPA的集合映射经常是适得其反的。只需发出一个单独的(非本地)查询以获取customerId = ...的订单即可。无论如何,我仍然认为这些踩并不合适。 - Stijn de Witt

0

以下是解决方案:

  1. 将实体层与API层分离,并仅在应用程序内部与API实例交互。在此上下文中,API也可以称为DTO。

  2. 完全从实体中删除关系。

  3. 创建一种机制来指示您希望获取子项。例如:将fetchRequestList传播到将API映射到实体的层(这样您就可以有条件地获取)。

  4. 在查询执行期间按照通常的方式收集父对象。

  5. 使用基于FK到父PK的IN子句的命名参数化查询检索整个子项集合。

  6. 循环遍历结果并将其与父项匹配。

这将强制ORM执行n+1个查询而不是n(n+1)个查询。请记住,现在您必须使用自定义逻辑实现级联保存、删除、更新等。


-1
在任何ORM框架中,N+1问题都是很常见的。你无法避免它。但是,更多的是关于采取什么样的方法来解决这个问题。你可以根据你的实现使用关联和惰性加载或急切加载数据。你也可以对数据库执行映射,并在单个查询中获取所有相关数据,然后将其映射到你的模型。由于数据库已经被索引了,所以这个操作可能比你使用N+1查询获取数据并进行映射要快(如果你的网络延迟允许的话)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接