【问题标题】:Hibernate OneToOne automatic join fetching (resolving n+1 problem)Hibernate OneToOne 自动join fetching(解决n+1问题)
【发布时间】:2011-08-11 12:09:07
【问题描述】:

Hibernate 3.3 存在 n+1 选择问题。

为简单起见,我只做一个简短的抽象示例。

假设我们有以下简单的类:

class MainEntity {
  @Id
  public Long id; //we have a table generator create this id

  @OneToOne ( mappedBy ="main" )
  public SubEntity subEntity;
}

class SubEntity {
 @Id
 @Column( name = "mainId" ) //note that this is the same column as the join column below
 public Long mainId; //in order to have the exact same id as the corresponding MainEntity

 @OneToOne ( fetch = FetchType.LAZY )
 @JoinColumn ( name = "mainId", insertable = false, updatable = false, nullable = false )
 public MainEntity main; //this is used for navigation and queries (" ... subentity.main = :x")
}

如您所见,SubEntityMainEntity 有一个关系,由两个属性表示,其中mainId 属性负责管理关系/外键。

这很好用,完全符合我们的需求。

但是,急切地加载SubEntityMainEntity 存在一个问题。

假设我有一个返回MainEntity 集合的查询。在当前设置下,Hibernate 将发出 n + 1 个选择:查询本身 + n 对每个 SubEntity 选择。

当然,我可以在查询中添加join fetch,但我更希望 Hibernate 自动执行此操作。因此我尝试添加@Fetch( FetchMode.JOIN ),但没有任何作用。

使用@Fetch( FetchMode.SUBSELECT ) 也没有问题,这应该将选择语句减少到 2 - 原始查询和子实体的选择(至少这是在另一个用 @CollectionOfElements 和 @987654333 注释的属性上发生的情况@)。


所以问题是:我将如何告诉 Hibernate 自动加入 fetch 或使用单个选择来急切地加载子实体?我错过了什么吗?

提前致谢,

托马斯

PS:可能有问题的一件事是 mappedBy = "main" 没有引用实际的 id 列,但我无法将其更改为 mappedBy = "id"

【问题讨论】:

    标签: java hibernate jpa


    【解决方案1】:

    如果您想在 MainEntity 和 SubEntity 之间共享主键,请使用 PrimaryKeyJoinColumnMapsId 注释。

    通过使用PrimaryKeyJoinColumn 加载实体 通过使用相同的主键将MainEntity 表与SubEntity 表连接起来。它应该可以解决 n+1 个问题。

    MapsId 注解要求 Hibernate 从 我们示例中的另一个关联实体会将SubEntity.mainEntity.id 复制到SubEntity.id

    @Entity
    public class MainEntity {
    
        @Id
        @GeneratedValue(strategy=GenerationType.IDENTITY)
        @Column(name = "main_Id")
        private Long id;
    
        @OneToOne(cascade = CascadeType.ALL)
        @PrimaryKeyJoinColumn
        private SubEntity  subEntity ;
    }
    
    
    @Entity
    public class SubEntity 
    {
        @Id @Column(name="main_Id_FK") Long id;
    
        @MapsId 
        @OneToOne
        @JoinColumn(name = "main_Id_FK")    
        @PrimaryKeyJoinColumn
        private MainEntity mainEntity;        
    
    }
    

    Hibernate 参考文档:

    PrimaryKeyJoinColumn
    MapsId

    【讨论】:

    • 我不知道@MapsId,谢谢。我会尝试一下。即使在 n+1 问题无法解决的情况下,我仍然更喜欢这种方法而不是我们目前的方法。只是一个问题:这是否允许我在没有引用MainEntity 的情况下实际设置 id?我们有创建缺少SubEntity 实例的情况,我们只有相应MainEntity 的ID。如果可能的话,我宁愿不必加载MainEntity 来设置参考。换句话说:如果id 有一个值,而mainEntitynull,会发生什么?
    • 我刚刚查看了您链接的@MapsId 文档,不幸的是,这个注释似乎是在 Hibernate 3.5(以及 JPA 2)中引入的。但是,我们目前卡在 JBoss 4.2.3(它不支持 JPA 2),因此无法使用 Hibernate 3.5(我们正在努力迁移到 JBoss 6,但这需要一段时间)。
    • @Thomas 回答您的第一条评论。如何仅使用 MainEntity 的 ID 创建 SubEntity。您需要使用EntityManager.getReference 来获取对MainEntity 的引用而不加载它,并将其设置为SubEntiy.main 关系而不加载Main。
    • 如果没有@MapsID,您必须在保存之前手动分配SubEntity.id,并设置SubEntity.main 关系insertable = false,updatable =false,nullable = false,就像在您的示例中一样。您可以继续使用@PrimaryKeyJoinColumn@ on MainEntity.subEntity` 这应该可以纠正选择问题。
    • 我现在测试了,但没有真正成功。使用没有 fetch 连接的查询时,我仍然会得到 n+1 个选择。添加@PrimaryKeyJoinColumn 不起作用,因为没有将insertable = false, updatable = false 添加到SubEntity.mainSubEntity.id
    【解决方案2】:

    有三个选项可以避免n+1的问题:

     Lot size
    
     subselect
    
     Make a LEFT JOIN in the query
    

    Here FAQ1 Here FAQ2

    【讨论】:

    • 两个链接都指向同一个页面,还有更具体的链接吗?另外:Lot Size:我猜你的意思是Batch size - 在这里不起作用,subselect - 我试过了,但正如已经提到的,它不起作用,left join - 我宁愿做一个 fetch join ,但如前所述,我不想将其添加到每个查询中。
    猜你喜欢
    • 2021-08-10
    • 2015-07-03
    • 1970-01-01
    • 2015-02-12
    • 2021-08-26
    • 2015-12-03
    • 2015-05-26
    • 2011-02-05
    • 2019-06-06
    相关资源
    最近更新 更多