在使用JPA获取集合时,避免N + 1和笛卡尔积问题的标准方法是什么

时间:2014-09-15 09:49:46

标签: java performance hibernate jpa orm

当一个实体的字段部分是集合时,人们希望获取具有尽可能少的查询数据并使用尽可能少的内存的数据。

第一个问题通过JPQL查询中的“join fetch”来解决(解决N + 1问题)。

然而,“连接提取”(通过检查相应的SQL查询很容易看到)会导致笛卡尔积问题:每个“行”对应于没有多重性的实体字段,返回结果集中存在多重性N_1 x N_2 x ... x N_m,其中N_1是第一个集合的多重性,N_2是第二个集合的多样性,N_m是第m个集合的多重性,假设实体有m个字段是集合。

Hibernate用FetchMode.SUBSELECT解决了这个问题(如果我没弄错的话,会进行m + 1个查询,每个查询都不返回冗余数据)。在JPA中解决此问题的标准方式是什么(在我看来,我不能混合,至少在这种情况下,JPA注释与Hibernate的注释)?

3 个答案:

答案 0 :(得分:4)

最安全的方式是用查询替换集合,尤其是当预期的大小足以导致性能问题时:

  1. 删除双向@OneToMany端,只留下拥有@ManyToOne端

  2. 您选择父实体(例如国家/地区),然后只需运行以下查询:

    select c from City c where c.country = :country
    select c from County c where c.country = :country
    select count(p), c.name from People p join p.country group by c.name
    
  3. 有关使用JPA和Hibernate映射集合的最佳方法的更多详细信息,请查看this article

答案 1 :(得分:0)

我试图添加@fetch(FetchMode.SUBSELECT)或@fetch(FetchMode.SELECT)它没有做任何改变,即(仍然进行连接而不是make subselect两个quires,它使所有选择在同一个查询中)

答案 2 :(得分:0)

当您尝试使用

int nextLeft=30; foreach (Names name in allNames) { Button tempButton = new Button(); tempButton.Name = name._id; tempButton.Text = name._name; tempButton.Location = new System.Drawing.Point(name.positionX + nextLeft,name.positionY); listView.Controls.Add(tempButton); nextLeft+=30; }

您将在entityManager.find(PoDetail.class, poNumber)实体中声明的所有列表中都没有使用笛卡尔积进行初始化,这也将具有重复项。当然,可以使用@OneToMany消除这些重复项,但Set不保留数据插入的顺序,当尝试在View中显示时,我们有乱码行。

我通过使用:

解决了这个问题

带参数的NamedQueries,以避免收集笛卡尔积的产品。

这样做您的视图数据将与持久数据插入顺序一样。

以下是示例代码:

父实体类:(它有更多的列表字段,我在这里提到一个)

Set

儿童实体类:

    @Entity
    @Table(name="PO_DETAILS")
    @NamedQuery(name="PoDetail.findByPoNumber", query="SELECT p FROM PoDetail p where p.poNumber=:poNumber")
        public class PoDetail implements Serializable {
    @Id
    @Column(name="PO_NUMBER", unique=true, nullable=false, length=30)
    private String poNumber;

    @Column(name="ACTION_TAKEN", length=2000)
    private String actionTaken;

    .....

    //bi-directional one-to-many association to PcrDetail

    @OneToMany(mappedBy="poDetail", cascade={CascadeType.ALL}, fetch=FetchType.EAGER, orphanRemoval=true)
    private List<PcrDetail> pcrDetails;

JPA DAO课程:

@Entity
@Table(name="PCR_DETAILS")
public class PcrDetail implements Serializable {

@Id
    @Column(name="PCR_NUMBER", unique=true, nullable=false, length=30)
    private String pcrNumber;

    @Column(name="CONTRACT_ID", length=30)
    private String contractId;
    .....

    //bi-directional many-to-one association to PoDetail

    @ManyToOne(cascade={CascadeType.ALL})
    @JoinColumn(name="PARENT_PO_NUMBER", insertable=false, updatable=false)
    private PoDetail poDetail;