Hibernate - 分页查询唯一结果

6
这似乎是一个众所周知的问题,可以在这里阅读到:http://blog.xebia.com/2008/12/11/sorting-and-pagination-with-hibernate-criteria-how-it-can-go-wrong-with-joins/,甚至在Hibernate FAQ中也有相关内容:https://community.jboss.org/wiki/HibernateFAQ-AdvancedProblems#Hibernate_does_not_return_distinct_results_for_a_query_with_outer_join_fetching_enabled_for_a_collection_even_if_I_use_the_distinct_keyword。此前也曾在SO上讨论过:How to get distinct results in hibernate with joins and row-based limiting (paging)?。问题在于,即使参考了所有这些资源,我仍然无法解决我的问题,这似乎与标准问题有点不同,尽管我不确定。标准解决方案涉及创建两个查询,第一个用于获取不同的ID,然后在更高级别的查询中使用这些ID来获取所需的分页。在我的情况下,Hibernate类类似于:
A
 - aId
 - Set<B>

B
 - bId 

在我看来,子查询似乎对我来说运行良好,并能够获取不同的aIds,但外部查询应该进行分页,却再次获取重复数据,因此子查询中的distinct没有效果。

假设我有一个A对象,其中包含一组四个B对象,我的分析是由于集合的引入,在获取数据时会出现问题。

session.createCriteria(A.class).list();

Hibernate正在将指向同一对象的四个引用填充到列表中。因此,标准解决方案对我无效。

请有人帮忙提出解决方案吗?

编辑:我已决定从不同的结果集自己进行分页。另一种同样糟糕的方法是延迟加载B对象,但这将需要为所有A对象查询获取相应的B对象。

4个回答

6

考虑使用类似这样的 DistinctRootEntity 结果转换器

session.createCriteria(A.class)
    .setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY).list();

更新

关于一对多关联的查询示例。

public Collection<Long> getIDsOfAs(int pageNumber, int pageSize) {
    Session session = getCurrentSession();

    Criteria criteria = session.createCriteria(A.class)
        .setProjection(Projections.id())
        .addOrder(Order.asc("id"));

    if(pageNumber >= 0 && pageSize > 0) {
        criteria.setMaxResults(pageSize);
        criteria.setFirstResult(pageNumber * pageSize);
    }

    @SuppressWarnings("unchecked")
    Collection<Long> ids = criteria.list();
    return ids;
}

public Collection<A> getAs(int pageNumber, int pageSize) {
    Collection<A> as = Collections.emptyList();

    Collection<Long> ids = getIDsOfAs(pageNumber, pageSize);
    if(!ids.isEmpty()) {
        Session session = getCurrentSession();

        Criteria criteria = session.createCriteria(A.class)
            .add(Restrictions.in("id", ids))
            .addOrder(Order.asc("id"))
            .setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
        @SuppressWarnings("unchecked")
        as = criteria.list(); 
    }    

    return as;
}

我也使用了子查询。我的子查询正常工作并返回唯一的ID。问题是外部查询正在获取重复项。例如,如果子查询返回A.ids = 1,2,3,则用于分页的主查询将获取重复的A.ids,如1,1,1,2,2,3,3,3,因此形式为Select A.* from A where A.id in (subquery) limit 10; 的完整查询无法获取唯一的A记录。 - Ashish
你最终的查询应该长成这个样子:select a.* from a where a.id in (:ids),用于获取A标识符的查询应该长成这个样子:select a.id from A limit 10。因此只要对标识符应用Limit,而不是应用到最终查询中。 - szhem
我的最终查询形式为select a.* from a left outer join b on a.id=b.aid,这需要完整的A对象。 - Ashish
请提供以下形式的结构体A和B的代码,以实现不同的分页功能。只实现其中一个是可以的,但同时实现两个却无法正常工作。 - Ashish
请查看更新。请注意,您必须对ID进行分页,而不是结果实体。 - szhem
显示剩余13条评论

1

您提到看到这个问题的原因是因为Set<B>被急切地获取了。如果您正在分页,很可能您不需要每个AB,所以最好是懒惰地获取它们。

然而,当您将B加入查询中进行选择时,同样会出现此问题。

在某些情况下,您不仅希望进行分页,还希望按照ID以外的其他字段进行排序。我认为做法是像这样制定查询:

  Criteria filter = session.createCriteria(A.class)
    .add(...任何您想要过滤的条件,包括别名等...);
  filter.setProjection(Projections.id());
Criteria paginate = session.createCriteria(A.class) .add(Subqueries.in("id", filter)) .addOrder(Order.desc("foo")) .setMaxResults(max) .setFirstResult(first);
return paginate.list();

(伪代码,没有检查语法是否完全正确,但您可以理解思路)


谢谢Arnout。但是你为什么认为这不会有重复项呢?我的问题是,即使对于像session.createCriteria(A.class).list()这样的小查询,由于急切获取,它也会返回重复项。你的解决方案如何防止急切获取? - Ashish
啊,是的:首先真正评估一下你是否真的需要急切获取。根据我的经验,总是急切获取某些集合几乎从来不是一个好主意 - 当然,在某些特定的查询中急切获取它们可能是有用的,并且 Criteria API 允许您指定它。如果你确定要始终急切获取这个集合,请在外部 Criteria(paginate)上使用 Criteria.DISTINCT_ROOT_ENTITY ResultTransformer - Arnout Engelen

0

我在这里回答了:使用Hibernate Criteria和DISTINCT_ROOT_ENTITY进行分页

你需要做三件事情,1)获取总数,2)获取你想要的行的ID,然后3)获取在步骤2中找到的ID的数据。一旦你正确排序,它就不是那么糟糕了,你甚至可以创建一个通用方法并发送一个分离的criteria对象来使它更抽象。


0
我使用了 groupBy 属性来实现这个功能。希望它能正常工作。
Criteria filter = session.createCriteria(A.class);       
filter.setProjection(Projections.groupProperty("aId"));
//filter.add(Restrictions.eq()); add restrictions if any
filter.setFirstResult(pageNum*pageSize).setMaxResults(pageSize).addOrder(Order.desc("aId"));

Criteria criteria = session.createCriteria(A.class);
criteria.add(Restrictions.in("aId",filter.list())).addOrder(Order.desc("aId"));
return criteria.list();

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接