查询JOIN FETCH性能问题

时间:2011-02-09 12:44:18

标签: java hibernate

我有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;

...

}

由于

4 个答案:

答案 0 :(得分:5)

来自JPA-Specifications

  

将setMaxResults或setFirstResult应用于查询的效果   涉及到集合的提取连接是未定义的。 (JPA“企业   JavaBeans 3.0,最终版本“,Kapitel 3.6.1查询接口)

Hibernate做正确的事情,但在内存中执行查询的一部分,这是非常慢的。在我的情况下,差异在3-5毫秒到400-500毫秒之间。

我的解决方案是在查询本身内实现分页。使用JOIN FETCH可以快速工作。

答案 1 :(得分:1)

在为JVM增加内存之后,事情变得更好了。毕竟我在查询中没有使用FETCH结束。

答案 2 :(得分:0)

如果您需要带有“fetch”的firstResult / maxResults,则可以在2个查询中拆分查询:

  1. 使用firstResult / maxResults查询实体ID,但子表上没有“fetch”:

    select entity.id from entity (without fetch) where .... (with firstResult/maxResults)
    
  2. 使用第一个查询返回的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());
    }
}

就是这样!