为什么要调用N + 1个查询?

时间:2016-08-29 12:48:42

标签: hibernate jpa criteria-api

我有一个非常复杂的条件查询运行JPA(Hibernate 4.x作为提供程序)。我试图提高性能,因为实际上我有一个N + 1查询问题。代码如下所示:

    @Override
public SearchResult<EntityA> findByAnyCritere(SomeCriteria critere, Integer page, Integer pageSize, String... orderBys) {
    CriteriaBuilder builder = getEntityManager().getCriteriaBuilder();

    // count all entities
    Long unfilteredCount = countAll();

    // count filtered entities
    Long count = unfilteredCount;

    CriteriaQuery<EntityA> criteriaQuery = builder.createQuery(EntityA.class);
    Root<EntityA> from = criteriaQuery.from(getEntityClass());
    Join<EntityA, EntityB> joinB = from.join("entityB", JoinType.LEFT);
    Join<EntityB, EntityC> joinC = joinB.join("entityC", JoinType.LEFT);
    Join<EntityB, EntityD> joinD = joinB.join("entityD", JoinType.LEFT);
    Join<EntityD, EntityE> joinE = joinD.join("entityE", JoinType.LEFT);

    Predicate predicate = builder.equal(from.<EntityF> get("entityF").<Long> get("id"), critere.getSomeId());
    Predicate predicateKeyword = filterLookupByValue(critere.getAnyCriteria(), builder, criteriaQuery, LookupType.TEXT_KEYWORD, keywordJoin);
    Predicate predicateNumberText = filterNumber(critere, builder, textJoin, false);

    Subquery<Long> subqueryLibelle = filterLibelle(critere.getAnyCriteria(), builder, criteriaQuery, from);

    if (StringUtils.isNotBlank(critere.getAnyCriteria())) {
        criteriaQuery.where(builder.and(predicate,
                builder.or(predicateKeyword, predicateNumberText, builder.exists(subqueryLibelle))));
    } else {
        criteriaQuery.where(builder.and(predicate));
    }

    criteriaQuery.select(from);

    count = JpaUtils.count(getEntityManager(), criteriaQuery);

    addSortingInstructionsIfNeeded(builder, criteriaQuery, from, orderBys);

    TypedQuery<EntityA> typedQuery = getEntityManager().createQuery(criteriaQuery);
    addPaginationRestrictionIfNeeded(typedQuery, page, pageSize);

    List<EntityA> entities = typedQuery.getResultList();
    LOG.debug("found {} entities", entities.size());

    return toSearchResult(unfilteredCount, count, entities, page, pageSize);
}

我试图理解为什么我会运行很多查询。例如,如果我加载大小为10个项目的第2页,我可以看到Hibernate首先运行这样的查询:

select * from ( 
  select inner2_.*, rownumber() over(order by order of inner2_) as rownumber_ from ( 
  select 
    <<fields>> 
  from 
    EntityA versionelk0_ 
    left outer join EntityB textversio1_ on versionelk0_.ID_TEXT_VERSION=textversio1_.ID 
    left outer join EntityC textentity2_ on textversio1_.ID_TEXT=textentity2_.ID 
    left outer join EntityD textdefini3_ on textversio1_.ID_TEXT_DEFINITION=textdefini3_.ID 
    left outer join EntityE lookupenti4_ on textdefini3_.ID_LOOKUP_KEYWORD=lookupenti4_.ID, 
    EntityF textentity6_ 
  where 
      textversio1_.ID_TEXT=textentity6_.ID and versionelk0_.ID_VERSION_ELKAT=41 
  order by textentity6_.NUMBER asc 
      fetch first 20 rows only 
  ) as inner2_ 
) as inner1_ 
where rownumber_ > 10 order by rownumber_

...它似乎将id提取到负载。

但在此之后,我可以看到启动了10个查询,每个查询都加载一个带有id的实体。

我希望只运行2个查询,第1个保持原样,但是第二个查询保留,如#34; WHERE ID(&lt;&gt;)&#34;

有人有解释(或更好,解决方案)吗?

编辑:相关映射(重新命名为保密)

@Entity
public class EntityA extends AbstractVersionedEntity<Long> {

    @Id
    @GeneratedValue
    @Column(name = "ID")
    private Long id;

    @ManyToOne
    @JoinColumn(name = "ID_TEXT_VERSION")
    private EntityB entityB;

[...]
}

@Entity
public class EntityB extends AbstractVersionedEntity<Long> {

    @Id
    @GeneratedValue
    @Column(name = "ID")
    private Long id;

    @OneToOne
    @JoinColumn(name = "ID_TEXT")
    private EntityC entityC;

    @OneToOne
    @JoinColumn(name = "ID_TEXT_DEFINITION")
    private EntityD definition;

[...]
}

@Entity
public class EntityC extends AbstractVersionedEntity<Long> {
    @Id
    @GeneratedValue
    @Column(name = "ID")
    private Long id;

    @Column(name = "NUMBER")
    private String number;

[...]
}

@Entity
public class EntityD extends AbstractVersionedEntity<Long> {
    @Id
    @GeneratedValue
    @Column(name = "ID")
    private Long id;

    @ManyToOne
    @JoinColumn(name = "ID_LOOKUP_CHAPTER")
    private EntityE chapter;

    @ManyToOne
    @JoinColumn(name = "ID_LOOKUP_CATEGORY")
    private EntityE category;

    @ManyToOne
    @JoinColumn(name = "ID_LOOKUP_KEYWORD")
    private EntityE keyword;

    @ManyToOne
    @JoinColumn(name = "ID_LOOKUP_RECIPIENT")
    private EntityE recipient;

[...]
}

@Entity
public class EntityE extends AbstractVersionedEntity<Long> {
    @Id
    @GeneratedValue
    @Column(name = "ID")
    private Long id;

    @Column(name = "KEY")
    private String key;

    @Column(name = "INDEX")
    private Integer index;

    @Column(name = "ACTIVE")
    private Boolean active = Boolean.TRUE;

    @ManyToOne
    @JoinColumn(name = "ID_LOOKUPTYPE")
    private EntityF type;

[...]
}

AbstractVersionedEntity类仅提供对@Version编号的支持,equals / hashCode / toString的默认实现和通用主键管理(在我的示例中)

大多数关系是延迟加载的,但我的问题不是属性是延迟加载的,而是我得到10个查询来加载10&#34; root&#34;实体。

由于

2 个答案:

答案 0 :(得分:0)

很难说没有任何映射会导致这种行为的原因。如果您的实体A中有一对多的集合,请尝试使用@BatchSize注释,如

@BatchSize(size = 10)
@OneToMany
private Set<EntityB> items = new HashSet<>();

通过这个注释,Hibernate将使用size属性指定批量获取集合的内容,这意味着将使用一个Select语句获取实体A,并使用一个包含IN子句的Select语句获取集合条目,最多包含10个IDS。

https://docs.jboss.org/hibernate/orm/5.0/javadocs/org/hibernate/annotations/BatchSize.html

答案 1 :(得分:0)

我误解了用N个查询查询了哪个实体。事实上,它是我的根实体之间的关系,所以我的整个帖子都没有意义......请忽略它。谢谢