我在服务器日志中收到警告“使用集合提取指定的firstResult / maxResults;在内存中应用!”。然而一切正常。但我不想要这个警告。
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"
答案 0 :(得分:20)
要避免此警告,您必须将呼叫getSingleResult
更改为
getResultList().get(0)
答案 1 :(得分:15)
此警告的原因是,当使用提取连接时,结果集中的顺序仅由所选实体的ID定义(而不是通过连接提取)。
如果内存中的这种排序导致问题,请不要将firsResult / maxResults与JOIN FETCH一起使用。
答案 2 :(得分:12)
虽然您获得了有效的结果,但SQL查询会获取所有数据,但效率并不高。
正如我在this article中所解释的,您有两种选择。
解决此问题的最简单方法是执行两个查询:
。第一个查询将获取与提供的过滤条件匹配的根实体标识符。 。第二个查询将使用先前提取的根实体标识符来获取父实体和子实体。
这种方法很容易实现,如下所示:
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();
第二种方法是在符合我们过滤条件的父实体和子实体的结果集上使用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"),
}
)
}
)
@NamedNativeQuery
提取与提供的标题匹配的所有Post实体及其关联的PostComment
子实体。 DENSE_RANK
窗口函数用于为每个Post和PostComment
连接记录分配排名,以便我们稍后可以过滤我们感兴趣的Post记录的数量。
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();
现在,默认情况下,像PostWithCommentByRank
这样的本机SQL查询会在同一个JDBC行中获取Post和PostComment
,因此我们最终会得到一个包含两个实体的Object []
但是,我们希望将表格Object[]
数组转换为父子实体树,因此我们需要使用Hibernate ResultTransformer
有关{{1}的更多详细信息},查看this article。
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
有关详细信息,请查看this article。
答案 3 :(得分:2)
此警告告诉您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";
答案 4 :(得分:0)
问题是你会得到笛卡尔积的JOIN。偏移量将削减您的记录集,而不会查看您是否仍在相同的根身份类
答案 5 :(得分:0)
我想emp有许多部门,这是一对多的关系。 Hibernate将使用获取的部门记录为此查询获取许多行。因此,在将结果真正提取到内存之前,无法确定结果集的顺序。因此,分页将在记忆中完成。
如果您不想使用emp获取部门,但仍希望根据部门进行某些查询,则可以在不发出警告的情况下实现结果而不进行内存排序。为此,您只需删除&#34; fetch&#34;条款。如下所示:
QUERY =&#34;来自员工的离开加入emp.salary sal left join emp.department dep其中emp.id =:id和dep.name =&#39; testing&#39;和sal.salary&gt; 5000&#34;