我有一个使用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_..。它似乎将ids提取到加载。
但在此之后,我可以看到启动了10个查询,每个查询都用其id加载一个实体。
我希望只运行2个查询,第一个保持原样,第二个使用类似于"WHERE ID in (<>)“的SQL片段。
有没有人有一个解释(或者更好的解决方案)?
编辑:相关映射(重命名以保证机密性)
@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个“根”实体。
谢谢
发布于 2016-08-29 22:12:58
在没有任何映射的情况下,很难说是什么导致了这种行为。如果您的实体A中有一个一对多集合,请尝试使用@BatchSize注解,如
@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
发布于 2016-08-30 22:20:22
我误解了哪个实体是用N个查询查询的。事实上,它是来自我的根实体的关系,所以我的整个帖子是没有意义的……请忽略它。谢谢
https://stackoverflow.com/questions/39206759
复制相似问题