Разбиение страниц с помощью критериев гибернации и DISTINCT_ROOT_ENTITY

Я уже выполнил разбивку на страницы, используя следующий код:

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), потому что, когда я создаю SQL вручную без отдельного, я получаю 11 результатов, и когда я использую DISTINCT, я получаю правильно, 8 отличных результатов.

Что не так с вышеуказанным кодом?

Ответы

Ответ 1

После долгого поиска решения моей проблемы мне удалось ее решить. Проблема в том, что если вы создаете критерии или запрос, который извлекает toMany ассоциации, используя JOINS, и то вы используете setMaxResults и установите ResultTransformer в DISTINCT_ROOT_ENTITY, результат не будет таким, каким вы ожидали.

Как сказал JB Nizet, предположим, что у вас есть 4 объекта A, каждый с 3 B-сущностями, и предположим, что ваш запрос извлекает все объекты A с их Bs.

В этом случае SQL-запрос вернет 12 строк. Если вы используете setMaxResults (7), он будет извлекать (например) три строки для A1 и его Bs, три строки для A2 и его Bs и только одну строку для A3 и ее первую B.

И поскольку вы использовали DISTINCT_ROOT_ENTITY, запрос критериев будет возвращать только три объекта: A1, A2 и A3 (которые будут иметь неполный набор Bs).

Чтобы решить эту проблему, вы должны установить FETCH MODE для отношений toMany (обычно коллекций) с SELECT или SUBSELECT, и у вас есть в основном 2 способа достижения этого:

Первый способ - использовать аннотацию @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

Ответ 2

Я не уверен, правильно ли я понял ваш вопрос, но если ваш запрос извлекает объекты с включенными ассоциациями toMany, то разбиение на страницы не будет работать должным образом.

Действительно, предположим, что у вас есть 4 сущности A, каждая из которых имеет 3 B-сущности, и предположим, что ваш запрос извлекает все объекты A с их Bs.

В этом случае SQL-запрос вернет 12 строк. Если вы используете setMaxResults (7), он будет извлекать (например) три строки для A1 и его Bs, три строки для A2 и его Bs и только одну строку для A3 и ее первый B.

И поскольку вы использовали DISTINCT_ROOT_ENTITY, запрос критериев будет возвращать только три объекта: A1, A2 и A3 (которые будут иметь неполный набор Bs).

Ответ 3

Это было проблемой для меня, и потребовалось некоторое время, чтобы придумать решение, которое работает для всех сценариев, которые у меня есть.

То, что вы хотите для каждой страницы разбивки на страницы, - это 2 вещи, общее количество всех результатов и одна страница результатов, но для этого вам нужно сделать 3 шага. 1) получить общее количество, 2) получить уникальные идентификаторы для вашей страницы и 3) получить полные данные для идентификаторов, найденных на шаге 2. И вы можете сделать все это с помощью одного объекта критерия:

1) получить общий счет, используя различные идентификаторы (uniqueField = имя вашего идентификатора в классе сущности)

  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) reset проекция и установка начала и длины (вам нужны различные идентификаторы)

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

3) reset и получить различные результаты, соответствующие ids

  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);