我有hibernate查询性能的问题,我无法弄清楚。在下面的代码片段中,我需要具有至少一个映射和过滤映射的选择实体。我正在使用FETCH JOIN来加载仅过滤的映射。 但在这种情况下,我的查询存在性能问题。 Hibernate说警告日志:
org.hibernate.hql.ast.QueryTranslatorImpl - 使用collection fetch指定的firstResult / maxResults;申请 存储器!
当我省略FETCH JOIN并且只留下JOIN查询时很快。但结果我将所有映射加载到实体,这对我来说是不可接受的状态。有没有办法提高查询性能?映射表中有很多行。
HQL查询:
select distinct e from Entity
join fetch e.mappings as mapping
where e.deleted = 0 and e.mappings is not empty
and e = mapping.e and mapping.approval in (:approvals)
实体:
@Entity
@Table(name="entity")
class Entity {
...
@OneToMany(mappedBy="entity", cascade=CascadeType.REMOVE, fetch=FetchType.LAZY)
@OrderBy("created")
private List<Mapping> mappings = new ArrayList<Mapping>();
...
}
@Entity
@Table(name="mapping")
class Mapping {
public static enum MappingApproval {
WAITING, // mapping is waiting for approval
APPROVED, // mapping was approved
DECLINED; // mapping was declined
}
...
@ManyToOne(fetch=FetchType.EAGER)
@JoinColumn(name="entity_id", nullable=false)
private Entity entity;
@Enumerated(EnumType.STRING)
@Column(name="approval", length=20)
private MappingApproval approval;
...
}
由于
答案 0 :(得分:5)
来自JPA-Specifications
将setMaxResults或setFirstResult应用于查询的效果 涉及到集合的提取连接是未定义的。 (JPA“企业 JavaBeans 3.0,最终版本“,Kapitel 3.6.1查询接口)
Hibernate做正确的事情,但在内存中执行查询的一部分,这是非常慢的。在我的情况下,差异在3-5毫秒到400-500毫秒之间。
我的解决方案是在查询本身内实现分页。使用JOIN FETCH可以快速工作。
答案 1 :(得分:1)
答案 2 :(得分:0)
如果您需要带有“fetch”的firstResult / maxResults,则可以在2个查询中拆分查询:
使用firstResult / maxResults查询实体ID,但子表上没有“fetch”:
select entity.id from entity (without fetch) where .... (with firstResult/maxResults)
使用第一个查询返回的ID上的“fetch”查询您的实体:
select entity from entity fetch ... where id in <previous ids>
答案 3 :(得分:0)
原因很慢是因为Hibernate根本没有分页执行SQL查询,而且限制是在内存中完成的。
但是,如果连接必须扫描并获取100k记录,而您只对100个结果感兴趣,那么提取器完成的99.9%的工作以及通过网络完成的所有I / O都是浪费。
正如我在this article中解释的那样,您可以轻松地转换使用JOIN FETCH和分页的JPQL查询:
List<Post> posts = entityManager.createQuery(
"select p " +
"from Post p " +
"left join fetch p.comments " +
"where p.title like :title " +
"order by p.id", Post.class)
.setParameter("title", titlePattern)
.setMaxResults(maxResults)
.getResultList();
进入SQL查询,使用父标识符使用DENSE_RANK限制结果:
@NamedNativeQuery(
name = "PostWithCommentByRank",
query =
"SELECT * " +
"FROM ( " +
" SELECT *, dense_rank() OVER (ORDER BY \"p.created_on\", \"p.id\") rank " +
" FROM ( " +
" SELECT p.id AS \"p.id\", " +
" p.created_on AS \"p.created_on\", " +
" p.title AS \"p.title\", " +
" pc.id as \"pc.id\", " +
" pc.created_on AS \"pc.created_on\", " +
" pc.review AS \"pc.review\", " +
" pc.post_id AS \"pc.post_id\" " +
" FROM post p " +
" LEFT JOIN post_comment pc ON p.id = pc.post_id " +
" WHERE p.title LIKE :titlePattern " +
" ORDER BY p.created_on " +
" ) p_pc " +
") p_pc_r " +
"WHERE p_pc_r.rank <= :rank ",
resultSetMapping = "PostWithCommentByRankMapping"
)
@SqlResultSetMapping(
name = "PostWithCommentByRankMapping",
entities = {
@EntityResult(
entityClass = Post.class,
fields = {
@FieldResult(name = "id", column = "p.id"),
@FieldResult(name = "createdOn", column = "p.created_on"),
@FieldResult(name = "title", column = "p.title"),
}
),
@EntityResult(
entityClass = PostComment.class,
fields = {
@FieldResult(name = "id", column = "pc.id"),
@FieldResult(name = "createdOn", column = "pc.created_on"),
@FieldResult(name = "review", column = "pc.review"),
@FieldResult(name = "post", column = "pc.post_id"),
}
)
}
)
查询可以像这样执行:
List<Post> posts = entityManager
.createNamedQuery("PostWithCommentByRank")
.setParameter(
"titlePattern",
"High-Performance Java Persistence %"
)
.setParameter(
"rank",
5
)
.unwrap(NativeQuery.class)
.setResultTransformer(
new DistinctPostResultTransformer(entityManager)
)
.getResultList();
要将表格结果集转换回实体图表,您需要ResultTransformer
,如下所示:
public class DistinctPostResultTransformer
extends BasicTransformerAdapter {
private final EntityManager entityManager;
public DistinctPostResultTransformer(
EntityManager entityManager) {
this.entityManager = entityManager;
}
@Override
public List transformList(
List list) {
Map<Serializable, Identifiable> identifiableMap =
new LinkedHashMap<>(list.size());
for (Object entityArray : list) {
if (Object[].class.isAssignableFrom(entityArray.getClass())) {
Post post = null;
PostComment comment = null;
Object[] tuples = (Object[]) entityArray;
for (Object tuple : tuples) {
if(tuple instanceof Identifiable) {
entityManager.detach(tuple);
if (tuple instanceof Post) {
post = (Post) tuple;
}
else if (tuple instanceof PostComment) {
comment = (PostComment) tuple;
}
else {
throw new UnsupportedOperationException(
"Tuple " + tuple.getClass() + " is not supported!"
);
}
}
}
if (post != null) {
if (!identifiableMap.containsKey(post.getId())) {
identifiableMap.put(post.getId(), post);
post.setComments(new ArrayList<>());
}
if (comment != null) {
post.addComment(comment);
}
}
}
}
return new ArrayList<>(identifiableMap.values());
}
}
就是这样!