Разбиение страниц с помощью критериев гибернации и 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);