如何避免警告“firstresult/maxresults specified with collection fetch;在内存中应用!“使用休眠时?

fnatzsnv  于 2021-07-03  发布在  Java
关注(0)|答案(6)|浏览(230)

我在服务器日志中得到一个警告“firstresult/maxresults specified with collection fetch;正在内存中应用!“。不过,一切正常。但我不想要这个警告。

我的密码是

public employee find(int id) {
    return (employee) getEntityManager().createQuery(QUERY).setParameter("id", id).getSingleResult();
}

我的问题是

QUERY = "from employee as emp left join fetch emp.salary left join fetch emp.department where emp.id = :id"
aurhwmvo

aurhwmvo1#

我猜emp有很多部门,这是一对多的关系。hibernate将为此查询获取许多行,其中包含已获取的部门记录。因此,只有当结果集真正返回到内存中时,才能确定结果集的顺序。所以分页将在内存中完成。
如果您不想使用emp获取部门,但仍然希望根据部门进行查询,则可以在不发出警告的情况下(不在内存中进行排序)获得结果。为此,您必须删除“fetch”子句。大致如下:
query=“from employee as emp left join emp.salary sal left join emp.department dep where emp.id=:id and dep.name='testing'and sal.salary>5000”

tktrz96b

tktrz96b2#

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

exdqitrt

exdqitrt3#

尽管您得到的是有效的结果,但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) "  +
    "order by p.createdOn", Post.class)
.setParameter("postIds", postIds)
.setHint(
    "hibernate.query.passDistinctThrough", 
    false
)
.getResultList();

修复了一个只能以只读模式获取实体的sql查询的问题

第二种方法是对符合筛选条件的父实体和子实体的结果集使用sdense\u rank,并仅限制前n个post条目的输出。
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"),
            }
        )
    }
)

这个 @NamedNativeQuery 获取与提供的标题匹配的所有post实体及其关联的 PostComment 子实体。这个 DENSE_RANK 窗口函数用于分配每个职位和职位的排名 PostComment 合并的记录,以便我们以后可以过滤我们感兴趣的post记录的数量。
这个 SqlResultSetMapping 提供sql级列别名与需要填充的jpa实体属性之间的Map。
现在,我们可以执行
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();

现在,默认情况下,本机sql查询 PostWithCommentByRank 有人会去拿邮递和信件 PostComment 在同一个jdbc行中,因此我们将以 Object[] 包含两个实体。
但是,我们要转换表格 Object[] 数组变成一棵父子实体树,为此,我们需要使用hibernate 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());
    }
}

这个 DistinctPostResultTransformer 必须分离正在获取的实体,因为我们正在覆盖子集合,并且不希望将其传播为实体状态转换:

post.setComments(new ArrayList<>());
ct2axkht

ct2axkht4#

出现此警告的原因是,使用fetch join时,结果集中的顺序仅由选定实体的id定义(而不是由fetch join定义)。
如果内存中的这种排序导致了问题,请不要将firsresult/maxresults与join fetch一起使用。

m4pnthwp

m4pnthwp5#

此警告告诉您hibernate正在内存中执行java分页。这会导致jvm内存消耗过高。因为开发人员可能会错过这个警告,所以我为hibernate添加了一个允许抛出异常的标志,而不是记录警告(https://hibernate.atlassian.net/browse/hhh-9965).
标志是hibernate.query.fail\u on \u pagination\u over \u collection\u 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";
5anewei6

5anewei66#

问题是你会得到笛卡尔积做连接。如果您仍在同一根标识类上,则偏移量将剪切记录集而不查看

相关问题