【问题标题】:Hibernate - distinct results with paginationHibernate - 分页的不同结果
【发布时间】:2012-03-14 03:59:35
【问题描述】:

这似乎是多年来众所周知的问题,可以在此处阅读: http://blog.xebia.com/2008/12/11/sorting-and-pagination-with-hibernate-criteria-how-it-can-go-wrong-with-joins/

甚至可以在 hibernate faqs 中找到参考:

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,然后在更高级别的查询中使用这些查询来获得所需的分页。在我的情况下,休眠类类似于

A
 - aId
 - Set<B>

B
 - bId 

在我看来,子查询对我来说似乎工作正常,并且能够获得不同的 aId,但应该进行分页的外部查询再次获取重复项,因此子查询中的不同是有没有效果。

假设我有一个 A 对象,其中包含一组四个 B 对象,我的分析是因为引入了集合,同时获取数据

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

hibernate 在列表中填充了四个仅指向一个对象的引用。因此,标准解决方案对我来说失败了。

有人可以帮忙想出这个案例的解决方案吗?

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

【问题讨论】:

    标签: java sql hibernate


    【解决方案1】:

    考虑像这样使用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 记录
    • 你的最终查询应该是这样的:select a.* from a where a.id in (:ids),并且查询来获取 A 的标识符应该是这样的:select a.id from A限制 10。因此,仅对标识符应用限制,而不是对最终查询。
    • 我的最终查询的形式是 select a.* from a left outer join b on a.id=b.aid,这是拥有完整的 A 对象所必需的。
    • 鉴于上面给出的 A 和 B 形式的结构,您能否给出既可以区分又可以分页的代码,只做其中一个可以正常工作,但不能同时做两个.
    • 看看更新。请注意,您必须对 ID 进行分页,而不是对结果实体进行分页。
    【解决方案2】:

    您提到您看到此问题的原因是因为Set&lt;B&gt; 被急切地获取。如果您正在分页,很可能您不需要每个 AB,因此最好懒惰地获取它们。

    但是,当您将B 加入查询以进行选择时,也会出现同样的问题。

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

    条件过滤器 = session.createCriteria(A.class) .add(...您要过滤的任何条件,包括别名等...); filter.setProjection(Projections.id()); 标准分页 = session.createCriteria(A.class) .add(Subqueries.in("id", filter)) .addOrder(Order.desc("foo")) .setMaxResults(最大值) .setFirstResult(first); 返回分页.list();

    (伪代码,没有检查语法是否完全正确,但你明白了)

    【讨论】:

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

    我在这里回答了这个问题:Pagination with Hibernate Criteria and DISTINCT_ROOT_ENTITY

    您需要做 3 件事,1) 获取总数,2) 获取所需行的 id,然后 3) 获取第 2 步中找到的 id 的数据。这真的不是那么糟糕一旦你得到了正确的顺序,你甚至可以创建一个泛型方法并向它发送一个分离的条件对象以使其更加抽象。

    【讨论】:

      【解决方案4】:

      我使用 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();
      

      【讨论】:

        猜你喜欢
        • 2011-02-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-06-04
        • 2015-02-26
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多