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

问题描述 投票:0回答:3

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

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

然而,“join fetch”(通过检查相应的 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 的注释混合)?

java performance hibernate jpa orm
3个回答
8
投票

最好的方法是用查询替换集合,特别是当预期大小足够大而导致性能问题时:

  1. 您删除了双向

    @OneToMany
    侧,只留下拥有的
    @ManyToOne

  2. 您选择父实体(例如

    Country
    )运行如下查询:

     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
    

0
投票

我尝试添加 @fetch(FetchMode.SUBSELECT) 或 @fetch(FetchMode.SELECT) 它不会做出任何更改,即(仍然进行连接而不是进行子选择两个查询,它会在同一查询中进行所有选择)


0
投票

当你尝试使用

entityManager.find(PoDetail.class, poNumber)

您将拥有在具有

@OneToMany
的实体中声明的所有列表,并使用笛卡尔积初始化次数,其中也会有重复项。当然,可以使用
Set
消除这些重复项,但是 Set 不会保留数据插入的顺序,当尝试在 View 中显示时,我们会出现乱序的行。

我通过使用解决了这个问题:

带有参数的NamedQueries以避免获取集合的笛卡尔积。

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

这是示例代码:

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

    @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;

子实体类:

@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;  

JPA DAO 类:

    public PoBean getPoDetails(PoBean poBean) {

    PoDetail poDetail = poBean.getPoDetail();
    String poNumber = poDetail.getPoNumber();
    entityManagerFactory = JpaUtil.getEntityManagerFactory();
    entityManager = entityManagerFactory.createEntityManager();
    try {

        try {

            poDetail = (PoDetail) entityManager
                    .createNamedQuery("PoDetail.findByPoNumber")
                    .setParameter("poNumber", poNumber).getSingleResult();

        } catch (NoResultException nre) {
            poBean.setStatusCode(PopVO.ERROR);
            poBean.setErrorMessage("No PO details foun with PO Number : "
                    + poNumber);
        }

        return poBean;

    } catch (Exception e) {
        e.printStackTrace();
        poBean.setStatusCode(PopVO.ERROR);
        poBean.setErrorMessage(e.getMessage());

        return poBean;

    } finally {
        entityManager.close();
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.