【问题标题】:Query with JOIN FETCH performance problem带有 JOIN FETCH 性能问题的查询
【发布时间】:2011-06-24 02:50:31
【问题描述】:

我无法弄清楚休眠查询性能的问题。在下面的代码 sn-p 中,我需要选择具有至少一个映射和过滤映射的实体。我为此使用 FETCH JOIN 来仅加载过滤后的映射。 但在那种情况下,我的查询存在性能问题。 Hibernate 说警告日志:

org.hibernate.hql.ast.QueryTranslatorImpl - 集合获取指定的 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;

...

}

谢谢

【问题讨论】:

  • 你能显示 Hibernate 发出的 SQL 查询吗?
  • @axtavt 我已经从 Hibenate 添加了 SQL 查询。感谢您的宝贵时间。
  • 查询是JOIN还是FETCH JOIN?另一个在哪里?
  • @axtavt 这个是 FETCH JOIN,只有 JOIN 变体有完全相同的查询,但没有后续的映射查询。

标签: java hibernate


【解决方案1】:

来自 JPA 规范

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

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

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

【讨论】:

  • 您应该真正指定如何执行此操作。这就是答案和出色答案之间的区别。 vladmihalcea.com/…
【解决方案2】:

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

  1. 使用 firstResult/maxResults 查询您的实体 ID,但不使用子表上的“获取”:

    select entity.id from entity (without fetch) where .... (with firstResult/maxResults)
    
  2. 使用您的第一个查询返回的 id 上的“获取”查询您的实体:

    select entity from entity fetch ... where id in <previous ids>
    

【讨论】:

    【解决方案3】:

    之所以慢是因为Hibernate在执行SQL查询时根本没有分页,而且限制是在内存中完成的。

    但是,如果连接必须扫描并获取 100k 条记录,而您只对 100 个结果感兴趣,那么提取器完成的 99.9% 的工作以及通过网络完成的所有 I/O 都只是浪费。

    您可以轻松转换同时使用 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());
        }
    }
    

    就是这样!

    【讨论】:

      【解决方案4】:

      在为 JVM 增加内存之后,情况会好很多。毕竟我最终没有在查询中使用 FETCH。

      【讨论】:

      • 在任何奇怪的情况下,只需增加堆大小:)
      猜你喜欢
      • 1970-01-01
      • 2013-05-13
      • 1970-01-01
      • 2012-02-24
      • 1970-01-01
      • 2020-11-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多