首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >为什么它调用N+1查询?

为什么它调用N+1查询?
EN

Stack Overflow用户
提问于 2016-08-29 20:48:42
回答 2查看 65关注 0票数 1

我有一个使用JPA (Hibernate 4.x作为提供者)运行的相当复杂的条件查询。我正在努力提高性能,因为实际上我遇到了一个N+1查询问题。代码如下所示:

代码语言:javascript
复制
    @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首先运行如下查询:

代码语言:javascript
复制
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_

..。它似乎将ids提取到加载。

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

我希望只运行2个查询,第一个保持原样,第二个使用类似于"WHERE ID in (<>)“的SQL片段。

有没有人有一个解释(或者更好的解决方案)?

编辑:相关映射(重命名以保证机密性)

代码语言:javascript
复制
@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 number、equals/hashCode/toString的默认实现和通用主键管理(在我的示例中)

大多数关系都是延迟加载的,但我的问题不是属性延迟加载,而是我有10个查询来加载10个“根”实体。

谢谢

EN

回答 2

Stack Overflow用户

发布于 2016-08-29 22:12:58

在没有任何映射的情况下,很难说是什么导致了这种行为。如果您的实体A中有一个一对多集合,请尝试使用@BatchSize注解,如

代码语言:javascript
复制
@BatchSize(size = 10)
@OneToMany
private Set<EntityB> items = new HashSet<>();

有了这个注解,Hibernate将以size属性指定的批处理来获取集合的内容,这意味着实体A将通过一条Select语句来获取,集合条目将通过一条Select语句和一条包含最多10个ids的in子句来获取。

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

票数 0
EN

Stack Overflow用户

发布于 2016-08-30 22:20:22

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

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/39206759

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档