【问题标题】:Hibernate creating N+1 queries for @ManyToOne JPA annotated propertyHibernate 为 @ManyToOne JPA 注释属性创建 N+1 个查询
【发布时间】:2015-03-04 04:00:28
【问题描述】:

我有这些课程:

@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 中获取所有记录会更好。实际上,我觉得奇怪为什么它不是默认行为。

那么,我该怎么做呢?

【问题讨论】:

    标签: java hibernate jpa many-to-one select-n-plus-1


    【解决方案1】:

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

    【讨论】:

      【解决方案2】:

      试试

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

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

      【讨论】:

      • 实际上它工作得很好,但是有没有办法使用注释来做到这一点?因为假设我们有很多 @ManyToOne 注释字段,我不想写很多 JOIN FETCH XXX 只是为了避免问题......
      • Hibernate 有一个您可以使用的 @Fetch 注释,将 @Fetch(FetchMode.JOIN) 放在您的关系映射上。
      • 即使使用@Fetch(FetchMode.JOIN),它仍然会创建额外的查询。我想我必须使用查询修复。
      • 也许@Fetch 在查询中被忽略,但如果您使用session.get() 获取实体,您可能会检查这是否属实。
      • 对我不起作用。 QueryException:查询指定连接提取,但提取关联的所有者不在选择列表中
      【解决方案3】:

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

      20.1.5. Using batch fetching

      小引用:

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

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

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

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

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

      • 集合/集合
      • 类/实体

      在这里也检查一下:

      【讨论】:

        【解决方案4】:

        这里的问题与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 project来断言JPA和Hibernate执行的语句数。

        【讨论】:

        • "但是,当执行一个没有显式使用 JOIN FETCH 子句的查询时,Hibernate 将不会使用 JOIN 来获取 FetchTYpe.EAGER 关联,因为它无法更改您已经指定的查询方式被构造。因此,它只能使用辅助查询。从我最近的测试中,我发现这不适用于 Criteria 查询。这是真的吗?
        • 您是否有一个可复制的测试用例表明这一点?
        • 由于我目前的引导方式很复杂,为此创建一个测试项目有点困难,但我会尝试并记住当我有机会时创建一个项目来显示这一点。基本思想是当我选择一个具有@ManyToOne 关系的实体时,在条件查询案例中,我看到一个正在执行的 SQL 查询(并且相关实体已加入)。在 JPQL 案例中,我看到正在执行两个 SQL 查询,第二个查询选择相关实体。我没有使用您的工具来计算查询,只是查看 SQL 输出(hibernate.show_sql = true)。
        • 你能看看这个问题吗? stackoverflow.com/questions/59849508/…
        猜你喜欢
        • 2015-10-03
        • 1970-01-01
        • 1970-01-01
        • 2011-12-07
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-03-22
        相关资源
        最近更新 更多