Java 我怎样才能避免警告;使用集合获取指定的firstResult/maxResults;在记忆中应用&引用;当使用Hibernate时?

Java 我怎样才能避免警告;使用集合获取指定的firstResult/maxResults;在记忆中应用&引用;当使用Hibernate时?,java,hibernate,jpa,jpql,ejb-3.0,Java,Hibernate,Jpa,Jpql,Ejb 3.0,我在服务器日志中收到一条警告:“使用集合获取指定的firstResult/maxResults;在内存中应用!”。不过,一切正常。但我不想要这个警告 我的代码是 public employee find(int-id){ return(employee)getEntityManager().createQuery(QUERY).setParameter(“id”,id).getSingleResult(); } 我的问题是 QUERY=“作为emp左加入获取emp.salary左加入获取emp

我在服务器日志中收到一条警告:“使用集合获取指定的firstResult/maxResults;在内存中应用!”。不过,一切正常。但我不想要这个警告

我的代码是
public employee find(int-id){
return(employee)getEntityManager().createQuery(QUERY).setParameter(“id”,id).getSingleResult();
}
我的问题是
QUERY=“作为emp左加入获取emp.salary左加入获取emp.department where emp.id=:id”

出现此警告的原因是,当使用fetch join时,结果集中的顺序仅由所选实体的ID定义(而不是由fetch join fetch定义)


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

要避免此警告,必须将调用
getSingleResult
更改为
getResultList().get(0)

问题是您将得到笛卡尔积。如果您仍然在同一个根标识类上,偏移量将在不查看的情况下剪切您的记录集

我猜emp有许多部门,这是一对多关系。Hibernate将为此查询获取许多行,其中包含已获取的部门记录。因此,在结果集真正将结果提取到内存之前,无法确定结果集的顺序。因此分页将在内存中完成

如果您不希望使用emp获取部门,但仍然希望基于该部门执行一些查询,则可以在不发出警告的情况下获得结果(无需在内存中进行排序)。为此,您必须删除“fetch”子句。所以大致如下:

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();
@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"),
            }
        )
    }
)
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());
    }
}

QUERY=“from employee as emp left join emp.salary sal left join emp.dep where emp.id=:id和dep.name=‘testing’和sal.salary>5000”

虽然您得到的是有效的结果,但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();
@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"),
            }
        )
    }
)
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());
    }
}
现在,默认情况下,像
PostWithCommentByRank
one这样的本机SQL查询将获取同一JDBC行中的Post和
PostComment
,因此我们将得到一个包含这两个实体的
对象[]

但是,我们希望将表格形式的
对象[]
数组转换为父子实体树,因此,我们需要使用Hibernate

DistinctPostResultTransformer
如下所示:

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();
@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"),
            }
        )
    }
)
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());
    }
}

此警告告诉您Hibernate正在内存中执行java分页。这可能会导致JVM内存消耗过高。 由于开发人员可能会错过此警告,因此我添加了一个允许引发异常的标志,而不是记录警告(),从而为Hibernate做出了贡献

该标志为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";

请在构建和执行query.JMelnik的位置添加代码,请查看我编辑的问题以及代码和查询。此外,请在其中设置maxResult参数。这是我的应用程序中的代码和查询。我没有手动设置maxResult。这意味着Hibernate正在获取所有内容,然后尝试在内存中应用first/max result限制。这对你来说可能不太合适。以下是JPA规范对这种交互的描述:“将
setMaxResults
setFirstResult
应用于涉及集合上的获取联接的查询的效果是未定义的。(,Kapitel 3.6.1查询接口)”,尽管投票可能已经表明:这确实是真的。即使对于不使用任何分页的查询,例如
setFirstResult(offset)
setMaxResults(limit)
,Hibernate在使用
getSingleResult
时也可能会抱怨这一点。(我假设Hibernate在内部进行了限制。)一个显著的区别是,当您希望返回1个结果的查询实际返回多个结果时,这不会引发异常。我会添加一些断言。尽管已经使用了getResultList(),但我在JOIN FETCH中收到了此警告。所以这不是解决这个问题的唯一可能的方法。第二个查询(加载“Posts”的那个)中不缺少排序吗?我们能相信数据库会按照ID给出的相同顺序返回帖子吗?我认为可以简单地将p.createdOn提供的
order
添加到第二个查询中……帖子已经按照第一个查询提供的正确顺序获取,您可以使用该
List
引用来保留顺序。第二个查询只是将未初始化的子关联替换为非代理的
列表
。这可能取决于数据库?在Postgres中,我还必须将ORDERBY子句添加到第二个查询中。似乎也证实了这一点……有人知道如何使用自定义谓词实现这一点吗?我正在尝试构建一个搜索端点。但似乎我无法同时组合自定义谓词、页面请求和本机SQL。。。