如何避免警告"使用集合提取指定的firstResult/maxResults;在内存中应用!" 什么时候使用Hibernate?

Pri*_*nce 27 java hibernate jpa jpql ejb-3.0

我在服务器日志中收到警告"使用集合提取指定的firstResult/maxResults;在内存中应用!" .然而一切正常.但我不想要这个警告.

我的代码是

public employee find(int id) {
    return (employee) getEntityManager().createQuery(QUERY).setParameter("id", id).getSingleResult();
}
Run Code Online (Sandbox Code Playgroud)

我的疑问是

QUERY = "from employee as emp left join fetch emp.salary left join fetch emp.department where emp.id = :id"
Run Code Online (Sandbox Code Playgroud)

Luc*_*cas 21

要避免此警告,您必须将呼叫更改getSingleResultgetResultList().get(0)

  • 一个值得注意的区别是,当您希望返回1结果的查询实际返回多个结果时,这不会引发异常.我会添加一些断言. (3认同)
  • 虽然投票可能已经表明了这一点:这确实是事实.即使对于不使用任何分页的查询,例如`setFirstResult(offset)`或`setMaxResults(limit)`,Hibernate在使用`getSingleResult`时仍可能会抱怨.(我假设Hibernate内部有限制.) (2认同)

Mik*_*unu 18

此警告的原因是,当使用提取连接时,结果集中的顺序仅由所选实体的ID定义(而不是通过连接提取).

如果内存中的这种排序导致问题,请不要将firsResult/maxResults与JOIN FETCH一起使用.


Vla*_*cea 17

虽然您获得了有效的结果,但SQL查询会获取所有数据,但效率并不高.

正如我在本文中解释的那样,您有两种选择.

修复了两个可以在读写模式下获取实体的SQL查询的问题

解决此问题的最简单方法是执行两个查询:

.第一个查询将获取与提供的过滤条件匹配的根实体标识符..第二个查询将使用先前提取的根实体标识符来获取父实体和子实体.

这种方法非常容易实现,如下所示:

List<Long> postIds = entityManager
.createQuery(
    "select p.id " +
    "from Post p " +
    "where p.title like :titlePattern " +
    "order by p.createdOn", Long.class)
.setParameter(
    "titlePattern",
    "High-Performance Java Persistence %"
)
.setMaxResults(5)
.getResultList();

List<Post> posts = entityManager
.createQuery(
    "select distinct p " +
    "from Post p " +
    "left join fetch p.comments " +
    "where p.id in (:postIds)", Post.class)
.setParameter("postIds", postIds)
.setHint(
    QueryHints.HINT_PASS_DISTINCT_THROUGH,
    false
)
.getResultList();
Run Code Online (Sandbox Code Playgroud)

使用一个只能以只读模式获取实体的SQL查询来解决问题

第二种方法是在与我们的过滤条件匹配的父实体和子实体的结果集上使用SDENSE_RANK,并仅限制前N个条目条目的输出.

SQL查询可以如下所示:

@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"),
            }
        )
    }
)
Run Code Online (Sandbox Code Playgroud)

@NamedNativeQuery获取所有帖子实体匹配与之相关的沿提供标题PostComment子实体.该DENSE_RANK窗口函数用于分配每个岗位等级和PostComment加入记录,使我们以后可以过滤只是邮政记录我们感兴趣的获取量.

SqlResultSetMapping提供了SQL级柱别名和需要被填充JPA实体属性之间的映射.

现在,我们可以这样执行PostWithCommentByRank @NamedNativeQuery:

List<Post> posts = entityManager
.createNamedQuery("PostWithCommentByRank")
.setParameter(
    "titlePattern",
    "High-Performance Java Persistence %"
)
.setParameter(
    "rank",
    5
)
.unwrap(NativeQuery.class)
.setResultTransformer(
    new DistinctPostResultTransformer(entityManager)
)
.getResultList();
Run Code Online (Sandbox Code Playgroud)

现在,默认情况下,像本机一样的本机SQL查询PostWithCommentByRank将获取Post和PostComment同一JDBC行,因此我们最终会得到一个包含两个实体的Object [].

但是,我们希望将表格Object[]数组转换为父子实体树,因此我们需要使用Hibernate.ResultTransformer有关详细信息ResultTransformer,请查看本文.

DistinctPostResultTransformer如下外观:

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());
    }
}
Run Code Online (Sandbox Code Playgroud)

DistinctPostResultTransformer必须分离的实体是牵强,因为我们要覆盖子集,我们不希望这样的传播作为一个实体的状态转换:

post.setComments(new ArrayList<>());
Run Code Online (Sandbox Code Playgroud)

有关详细信息,请查看此文章.

  • 有谁知道如何使用自定义谓词使其工作?我正在尝试构建一个搜索端点。但似乎我无法同时将自定义谓词、页面请求和本机 SQL 结合起来...... (2认同)

Réd*_*oui 8

此警告告诉您 Hibernate 正在内存中执行 java 分页。这可能会导致高 JVM 内存消耗。由于开发人员可能会错过此警告,因此我通过添加一个允许抛出异常而不是记录警告的标志来为 Hibernate 做出贡献(https://hibernate.atlassian.net/browse/HHH-9965)。

标志是hibernate.query.fail_on_pagination_over_collection_fetch

我建议大家启用它。

该标志在org.hibernate.cfg.AvailableSettings 中定义:

    /**
     * Raises an exception when in-memory pagination over collection fetch is about to be performed.
     * Disabled by default. Set to true to enable.
     *
     * @since 5.2.13
     */
    String FAIL_ON_PAGINATION_OVER_COLLECTION_FETCH = "hibernate.query.fail_on_pagination_over_collection_fetch";
Run Code Online (Sandbox Code Playgroud)