繁体   English   中英

Hibernate 为 @ManyToOne JPA 注释属性创建 N+1 个查询

[英]Hibernate creating N+1 queries for @ManyToOne JPA annotated property

我有这些课程:

@Entity
public class Invoice implements Serializable {
    @Id
    @Basic(optional = false)
    private Integer number;

    private BigDecimal value;

    //Getters and setters
}

@Entity
public class InvoiceItem implements Serializable {
    @EmbeddedId
    protected InvoiceItemPK invoiceItemPk;

    @ManyToOne
    @JoinColumn(name = "invoice_number", insertable = false, updatable = false)
    private Invoice invoice;

    //Getters and setters
}

当我运行此查询时:

session.createQuery("select i from InvoiceItem i").list();

它执行一个查询以从 InvoiceItem 中选择记录,如果我有 10000 个发票项目,它会生成 10000 个附加查询以从每个 InvoiceItem 中选择发票。

我认为如果可以在单个 sql 中获取所有记录会好得多。 实际上,我觉得奇怪为什么它不是默认行为。

那么,我该怎么做呢?

这里的问题与Hibernate无关,而是与JPA有关。

在 JPA 1.0 之前,Hibernate 3 对所有关联使用延迟加载。

但是,JPA 1.0 规范仅将FetchType.LAZY用于集合关联:

@ManyToOne@OneToOne关联默认使用FetchType.EAGER ,从性能角度来看这是非常糟糕的。

此处描述的行为称为 [N+1 查询问题][5],这是因为 Hibernate 需要确保在将结果返回给用户之前初始化@ManyToOne关联。

现在,如果您通过entityManager.find使用直接获取,Hibernate 可以使用 LEFT JOIN 来初始化FetchTYpe.EAGER关联。

但是,当执行未显式使用 JOIN FETCH 子句的查询时,Hibernate 不会使用 JOIN 来获取FetchTYpe.EAGER关联,因为它无法更改您已经指定如何构造的查询。 因此,它只能使用辅助查询。

修复很简单。 只需将FetchType.LAZY用于所有关联:

   @ManyToOne(fetch = FetchType.LAZY)
   @JoinColumn(name = "invoice_number", insertable = false, updatable = false)
   private Invoice invoice;

此外,您应该使用db-util 项目来断言 JPA 和 Hibernate 执行的语句数。

试试

session.createQuery("select i from InvoiceItem i join fetch i.invoice inv").list();

它应该使用连接获取单个 SQL 查询中的所有数据。

是的,您需要设置: @BatchSize(size=25) 在这里检查:

20.1.5. 使用批量获取

小引用:

使用批量获取,如果访问了一个代理,Hibernate 可以加载多个未初始化的代理。 批量抓取是对惰性选择抓取策略的优化。 您可以通过两种方式配置批量提取:在类级别和集合级别。

类/实体的批量获取更容易理解。 考虑以下示例:在运行时,您在一个会话中加载了 25 个 Cat 实例,每个 Cat 都有一个对其所有者 Person 的引用。 Person 类使用代理来映射,lazy="true"。 如果您现在遍历所有猫并对每只猫调用 getOwner(),Hibernate 将默认执行 25 条 SELECT 语句来检索代理所有者。 您可以通过在 Person 的映射中指定批量大小来调整此行为:

<class name="Person" batch-size="10">...</class>

指定这个批量大小后,Hibernate 现在将在需要访问未初始化的代理时按需执行查询,如上,但不同的是,它不是查询正在访问的确切代理实体,而是一次查询更多 Person 的所有者,因此,当访问其他人的所有者时,它可能已经被这个批量提取初始化,只有少数(远少于 25 个)查询将被执行。

因此,我们可以在两者上使用该注释:

  • 集合/集
  • 类/实体

也在这里检查:

在此方法中,触发了多个 SQL。 第一个被触发以检索父表中的所有记录。 其余的用于检索每个父记录的记录。 第一个查询从数据库中检索 M 条记录,在本例中为 M 条父记录。 对于每个父级,一个新的查询检索子级。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM