【问题标题】:Pagination with Hibernate Criteria and DISTINCT_ROOT_ENTITY使用 Hibernate Criteria 和 DISTINCT_ROOT_ENTITY 进行分页
【发布时间】:2012-06-17 18:56:41
【问题描述】:

我已经使用以下代码实现了分页:

public Paginacao<Anuncio> consultarPaginado(int pagina, Integer cidadeId) {

            Criteria criteria = this.sessionFactory.getCurrentSession().createCriteria(Anuncio.class);      
            criteria.add(Restrictions.eq("ativo", true));
            criteria.add(Restrictions.eq("statusLiberacao", AnunciosUtil.STATUS_ANUNCIO_LIBERADO));
            criteria.add(Restrictions.eq("statusVendaAnuncio", AnunciosUtil.STATUS_VENDA_ANUNCIO_DISPONIVEL));

            if (cidadeId != null) {
                criteria.add(Restrictions.eq("cidade.id", cidadeId));
            }

            criteria.addOrder(Order.desc("dataPostagem"));
            criteria.setProjection(Projections.rowCount());

            Long count = (Long) criteria.uniqueResult();

            Paginacao<Anuncio> paginacao = new Paginacao<Anuncio>();
            int qtdPaginas = (count.intValue() / 7) + 1;

            paginacao.setQtdPaginas(qtdPaginas);

            criteria.setProjection(null);// reseta a criteria sem a projeção
            criteria.setResultTransformer(CriteriaSpecification.DISTINCT_ROOT_ENTITY);

            if (pagina > qtdPaginas) {
                pagina = qtdPaginas;
            }
            pagina = pagina - 1;
            criteria.setFirstResult(pagina * ConstantesGenericas.MAXIMO_OBJETOS_RETORNADOS);
            criteria.setMaxResults(ConstantesGenericas.MAXIMO_OBJETOS_RETORNADOS);

            paginacao.setRegistros(criteria.list());

            return paginacao;
        }

当我手动构建 SQL 查询并将其提交到数据库时,我得到 8 个结果。但是,当我尝试上面的代码时,在将 ResultTransformer 设置为 DISTINCT_ROOT_ENTITY e 之前得到 8 个结果(没有不同),在设置之后我得到 4 个结果。但我应该得到 8 个结果(使用 DISTINCT),因为当我手动构建没有 distinct 的 SQL 时,我得到 11 个结果,而当我使用 DISTINCT 时,我得到正确的 8 个不同结果。

上面的代码有什么问题?

【问题讨论】:

    标签: hibernate hibernate-criteria


    【解决方案1】:

    在为我的问题寻找解决方案很长时间之后,我设法解决了它。如果您创建使用 JOINS 检索 toMany 关联的条件或查询,以及 然后你使用 setMaxResults 并将 ResultTransformer 设置为 DISTINCT_ROOT_ENTITY 结果不会像你预期的那样。

    正如 JB Nizet 所说,假设您有 4 个 A 实体,每个实体有 3 个 B 实体,并假设您的查询检索所有 A 实体及其 B。

    在这种情况下,SQL 查询将返回 12 行。如果您使用 setMaxResults(7),它将检索(例如)A1 及其 Bs 的三行,A2 及其 Bs 的三行,A3 及其第一个 B 仅检索 1 行。

    并且由于您使用了 DISTINCT_ROOT_ENTITY,因此条件查询将仅返回三个实体:A1、A2 和 A3(它们将包含一组不完整的 B)。

    要解决这个问题,您必须将 toMany(通常是集合)关系的 FETCH MODE 设置为 SELECT 或 SUBSELECT,基本上有两种方法可以实现:

    第一种方法是在属性上使用@FetchMode(FetchMode.SUBSELECT) 注释,我不喜欢这种方法,因为它会导致每个查询都使用 SUBSELECT FETCH 来检索集合。但它会起作用。

    另一种方法是在构建查询时为关系设置获取模式。我更喜欢这种方式,因为我可以根据需要自定义查询,并且不必对所有查询都使用 SUBSELECTS。所以,我是这样做的:

    public Paginacao<Anuncio> consultarPaginado(int pagina, Integer cidadeId) {
    
            Criteria criteria = this.sessionFactory.getCurrentSession().createCriteria(Anuncio.class);      
            criteria.add(Restrictions.eq("ativo", true));
            criteria.add(Restrictions.eq("statusLiberacao", AnunciosUtil.STATUS_ANUNCIO_LIBERADO));
            criteria.add(Restrictions.eq("statusVendaAnuncio", AnunciosUtil.STATUS_VENDA_ANUNCIO_DISPONIVEL));
            criteria.setFetchMode("imagens", FetchMode.SELECT);
            criteria.setFetchMode("pagamentos", FetchMode.SELECT);      
    
            if (cidadeId != null) {
                criteria.add(Restrictions.eq("cidade.id", cidadeId));
            }
    
            criteria.addOrder(Order.desc("dataPostagem"));
            criteria.setProjection(Projections.rowCount());
    
            Long count = (Long) criteria.uniqueResult();
    
            Paginacao<Anuncio> paginacao = new Paginacao<Anuncio>();
            int qtdPaginas = (count.intValue() / 7) + 1;
    
            paginacao.setQtdPaginas(qtdPaginas);
    
            criteria.setProjection(null);// reseta a criteria sem a projeção
            criteria.setResultTransformer(CriteriaSpecification.DISTINCT_ROOT_ENTITY);
    
            if (pagina > qtdPaginas) {
                pagina = qtdPaginas;
            }
            pagina = pagina - 1;
            criteria.setFirstResult(pagina * ConstantesGenericas.MAXIMO_OBJETOS_RETORNADOS);
            criteria.setMaxResults(ConstantesGenericas.MAXIMO_OBJETOS_RETORNADOS);
    
            paginacao.setRegistros(criteria.list());
    
            return paginacao;
        }
    

    希望它对其他人有所帮助。 ;D

    【讨论】:

    • 我真的希望你的变量名不是葡萄牙语:)
    • 仍然无法使其工作.. 尝试将 FetchMode.SELECT 添加到所有 *ToMany 集合但仍然没有结果..
    • 谢谢!它不仅对我有用,而且我几天来一直在寻找解决方案。您找到了解决方案和解释!我真的很喜欢这篇文章!!!
    • 我在一个只搜索主表上的条件的查询中完成了这项工作,fetchMode 成功地将内部连接更改为选择。但在检查关联实体属性的查询中不起作用 - 连接似乎是不可避免的。
    【解决方案2】:

    我不确定我是否正确理解了您的问题,但如果您的查询检索到带有 toMany 关联联接的实体,则分页将无法按预期工作。

    确实,假设您有 4 个 A 实体,每个实体有 3 个 B 实体,并假设您的查询检索所有 A 实体及其 B。

    在这种情况下,SQL 查询将返回 12 行。如果使用 setMaxResults(7),它将检索(例如)A1 及其 Bs 的三行,A2 及其 Bs 的三行,A3 及其第一个 B 仅检索 1 行。

    并且由于您使用了 DISTINCT_ROOT_ENTITY,因此条件查询将仅返回三个实体:A1、A2 和 A3(它们将包含一组不完整的 B)。

    【讨论】:

    • 是的,我知道了,我已经解决了。蚂蚁,和你说的完全一样。我会发布答案。
    【解决方案3】:

    这对我来说是个问题,我花了一些时间才想出一个适用于我所有场景的解决方案。

    您希望每个分页页面有两件事,即所有结果的总数和您的单页结果,但要做到这一点,您需要采取 3 个步骤。 1) 获取总数,2) 获取页面的唯一 ID,以及 3) 获取第 2 步中找到的 id 的完整数据。您可以使用单个条件对象完成所有这些操作:

    1) 使用不同的 id 获取总数(uniqueField = 您在实体类中的 id 的名称)

      Criteria criteria = session.createCriteria(YourEntity.class);
      Projection idCountProjection = Projections.countDistinct(uniqueField);
      criteria.setProjection(idCountProjection);
      //setup criteria, joins etc here
      int totalResultCount = ((Long)criteria.uniqueResult()).intValue();
    

    2) 重置投影并设置开始和长度(您想要不同的 id)

      criteria.setProjection(Projections.distinct(Projections.property(uniqueField)));
      criteria.setFirstResult(start); 
      criteria.setMaxResults(length);
      List uniqueSubList = criteria.list();
    

    3) 重置投影并获得与 id 匹配的不同结果

      criteria.setProjection(null);
      criteria.setFirstResult(0); criteria.setMaxResults(Integer.MAX_VALUE);
      criteria.add(Restrictions.in(uniqueField, uniqueSubList));
      criteria.setResultTransformer(CriteriaSpecification.DISTINCT_ROOT_ENTITY);
      List searchResults = criteria.list();
      //and now, however you want to return your results
      Map<String, Object> searchResultsMap = new HashMap<String, Object>();
      searchResultsMap.put("searchResults", searchResults);
      searchResultsMap.put("totalResultCount", totalResultCount);
    

    【讨论】:

    • 这太棒了!不过需要注意的是:如果您使用 order by 某些数据库,则需要 order by 列作为第二个查询的结果集的一部分。有关示例,请参见 h2database.com/javadoc/org/h2/constant/ErrorCode.html#c90068
    • 正确的分页需要对用户有意义的排序;例如,按名称。但步骤 2) 只有唯一字段。如何编写“select distinct uniqueField from (select * from my_table order by my_field)”作为 Hibernate 标准? p.s. @jontejj 该链接已失效。
    • 在这里发现了第二个问题:这提出了一个关键假设,即在唯一字段上执行 distinct/project 将保留订单。我不认为这是安全的。
    猜你喜欢
    • 2018-08-24
    • 1970-01-01
    • 2015-03-15
    • 1970-01-01
    • 2011-09-15
    • 2013-12-10
    • 2011-07-17
    • 2011-03-14
    • 2016-06-22
    相关资源
    最近更新 更多