Как получить четкие результаты в спящем режиме с объединениями и ограничением по строкам (разбиение на страницы)?

Я пытаюсь реализовать разбиение на страницы с использованием ограничения на основе строк (например: setFirstResult(5) и setMaxResults(10)) в запросе Hibernate Criteria, который присоединяется к другим таблицам.

Понятно, что данные обрезаются случайным образом; и причина этого объясняется

В качестве решения на странице предлагается использовать «второй выбор sql» вместо соединения.

Как я могу преобразовать мой существующий запрос критериев (который имеет соединения с использованием createAlias()), чтобы вместо этого использовать вложенный выбор?


person Daniel Alexiuc    schedule 18.11.2008    source источник


Ответы (10)


Вы можете достичь желаемого результата, запросив список отдельных идентификаторов вместо списка отдельных гидратированных объектов.

Просто добавьте это к своим критериям:

criteria.setProjection(Projections.distinct(Projections.property("id")));

Теперь вы получите правильное количество результатов в соответствии с вашим строковым ограничением. Причина, по которой это работает, заключается в том, что проекция будет выполнять проверку отличимости как часть запроса sql, а не то, что делает ResultTransformer, который фильтрует результаты на отличимость после sql запрос был выполнен.

Стоит отметить, что вместо получения списка объектов теперь вы получите список идентификаторов, которые вы можете использовать для гидратации объектов из спящего режима позже.

person Community    schedule 19.11.2008
comment
Я получаю сообщение об ошибке, когда добавляю это в свой DetachedCriteria. Невозможно выполнить поиск [SQL: SQL недоступен]. Есть ли у вас какие-нибудь идеи? - person Barbaros Alp; 13.02.2009
comment
У меня отлично работает - возможно, проверьте, действительно ли у вас есть идентификатор с именем id - person Daniel Alexiuc; 19.02.2009
comment
FishBoy - это я на самом деле. Еще в '08 тебе не разрешали отвечать на свои вопросы. - person Daniel Alexiuc; 14.03.2012
comment
Как вы потом увлажняете объекты? - person Gavin Haynes; 23.11.2016
comment
но он возвращает вам только выбранное свойство, например ›id.exampleA, он вернет только список со значениями exampleA, а не с классом, вы недооцениваете то, что я пытаюсь сказать? как сделать так, чтобы он вернулся в класс? Благодарность - person Alberto Acuña; 25.04.2017
comment
Мы можем использовать HQL для получения отдельных строк на основе отдельного идентификатора. - person MR AND; 28.12.2018

Я использую этот со своими кодами.

Просто добавьте это к своим критериям:

критериев.setResultTransformer (Criteria.DISTINCT_ROOT_ENTITY);

этот код будет похож на выбранную * из таблицы собственного sql. Надеюсь, это поможет.

person grayshop    schedule 03.12.2009
comment
В этом случае это не сработает - см. Ответ FishBoy, в котором объясняется, почему. - person Daniel Alexiuc; 04.12.2009
comment
Кроме того, согласно ссылке, предоставленной Даниэлем Алексюком в его вопросе, это не всегда переводится в отдельный пункт в собственном sql. Но это работает, если вам не нужно разбивать страницы. - person Alberto de Paola; 25.07.2012
comment
отвергнуты, поскольку этот ответ просто неверен как в контексте этого вопроса, так и в отношении его содержания, как описано здесь [stackoverflow.com/questions/25536868/ этот отдельный через ResultSetTransformer выполняется после запроса казнен - person user2039709; 30.09.2015
comment
Совершенно неверный ответ, не работает с лимитом результатов, людям, проголосовавшим за, лимит не нужен - person che javara; 22.02.2017

Небольшое улучшение, основанное на предложении FishBoy.

Такой запрос можно выполнить за одно обращение, а не за два отдельных этапа. то есть один запрос ниже будет правильно отображать отдельные результаты, а также возвращать сущности, а не только идентификаторы.

Просто используйте DetachedCriteria с проекцией идентификатора в качестве подзапроса, а затем добавьте значения разбиения на страницы для основного объекта Criteria.

Это будет выглядеть примерно так:

DetachedCriteria idsOnlyCriteria = DetachedCriteria.forClass(MyClass.class);
//add other joins and query params here
idsOnlyCriteria.setProjection(Projections.distinct(Projections.id()));

Criteria criteria = getSession().createCriteria(myClass);
criteria.add(Subqueries.propertyIn("id", idsOnlyCriteria));
criteria.setFirstResult(0).setMaxResults(50);
return criteria.list();
person Daniel Alexiuc    schedule 27.10.2011
comment
Я думаю, что этот ответ намного более полный и действительно заполняет ответ о том, как гидратировать отдельный список связанного объекта. Это именно то, что я искал. Спасибо. На самом деле, я думаю, что это лучший ответ. - person JamesD; 29.08.2013
comment
Пробовал это. Не работает. Подзапрос работает, но основной запрос по-прежнему не ограничен отдельным запросом. - person Gary Kephart; 10.10.2013
comment
Я сэкономил много времени благодаря этому ответу, большое спасибо. - person Rodrigo Almeida; 11.06.2015
comment
Это отлично работает, но следующий вопрос: как мне получить общий размер результата для idsOnlyCriteria? Часто при разбивке по страницам вы хотите знать, сколько всего страниц / итераций имеется. - person Casey; 16.07.2016
comment
Я могу убедиться, что это не работает после тестирования, мы все равно будем извлекать дубликаты в запросе критериев, что испортит разбивку на страницы / ограничение. - person che javara; 22.02.2017

Небольшое улучшение предложения @ FishBoy заключается в использовании проекции идентификатора, поэтому вам не нужно жестко кодировать имя свойства идентификатора.

criteria.setProjection(Projections.distinct(Projections.id()));
person nikita    schedule 08.06.2011

Решение:

criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);

работает очень хорошо.

person JJ.    schedule 16.04.2010
comment
Это отлично работает для обычных запросов. Но этот вопрос конкретно касается запросов Hibernate, которые используют ограничение на основе строк или разбиение на страницы. - person Daniel Alexiuc; 03.06.2010
comment
... и это имеет соединения с другими таблицами. - person Daniel Alexiuc; 03.06.2010

session = (Session) getEntityManager().getDelegate();
Criteria criteria = session.createCriteria(ComputedProdDaily.class);
ProjectionList projList = Projections.projectionList();
projList.add(Projections.property("user.id"), "userid");
projList.add(Projections.property("loanState"), "state");
criteria.setProjection(Projections.distinct(projList));
criteria.add(Restrictions.isNotNull("this.loanState"));
criteria.setResultTransformer(Transformers.aliasToBean(UserStateTransformer.class));

Это мне помогло: D

person Andoy Abarquez    schedule 24.01.2013

если вы хотите использовать ORDER BY, просто добавьте:

criteria.setProjection(
    Projections.distinct(
        Projections.projectionList()
        .add(Projections.id())
        .add(Projections.property("the property that you want to ordered by"))
    )
);
person rekinyz    schedule 14.07.2014
comment
не могли бы вы уточнить, почему это сработает. и как мне установить порядок по нескольким столбцам и добавить по возрастанию или по убыванию? - person Stoppal; 05.08.2014

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

Преимущество этого решения в том, что оно:

  • быстрее, чем решение PK id, упомянутое в этой статье
  • сохраняет порядок и не использует предложение in в возможно большом наборе данных PK

Полную версию статьи можно найти на мой блог

Hibernate дает возможность определять метод выборки ассоциаций не только во время разработки, но и во время выполнения путем выполнения запроса. Таким образом, мы используем этот подход в сочетании с простыми средствами релаксации, а также можем автоматизировать процесс изменения алгоритма выборки свойств запроса только для свойств коллекции.

Сначала мы создаем метод, который разрешает все свойства коллекции из класса Entity:

public static List<String> resolveCollectionProperties(Class<?> type) {
  List<String> ret = new ArrayList<String>();
  try {
   BeanInfo beanInfo = Introspector.getBeanInfo(type);
   for (PropertyDescriptor pd : beanInfo.getPropertyDescriptors()) {
     if (Collection.class.isAssignableFrom(pd.getPropertyType()))
     ret.add(pd.getName());
   }
  } catch (IntrospectionException e) {
    e.printStackTrace();
  }
  return ret;
}

После этого вы можете использовать этот небольшой вспомогательный метод, который посоветует вашему объекту критериев изменить FetchMode на SELECT для этого запроса.

Criteria criteria = …

//    … add your expression here  …

// set fetchmode for every Collection Property to SELECT
for (String property : ReflectUtil.resolveCollectionProperties(YourEntity.class)) {
  criteria.setFetchMode(property, org.hibernate.FetchMode.SELECT);
}
criteria.setFirstResult(firstResult);
criteria.setMaxResults(maxResults);
criteria.list();

Это отличается от определения FetchMode ваших сущностей во время разработки. Таким образом, вы можете использовать обычную выборку ассоциации соединения для алгоритмов разбиения по страницам в пользовательском интерфейсе, потому что в большинстве случаев это не критическая часть, и более важно получить результаты как можно быстрее.

person Andreas Hartmann-schneevoigt    schedule 06.07.2012
comment
Таким образом, вы не получите заполненные коллекции после закрытия сеанса или отсоединения результата критерия. - person antgar9; 23.11.2016

Ниже показано, как мы можем выполнить множественное проецирование, чтобы получить отчетливое изображение.

    package org.hibernate.criterion;

import org.hibernate.Criteria;
import org.hibernate.Hibernate;
import org.hibernate.HibernateException;
import org.hibernate.type.Type;

/**
* A count for style :  count (distinct (a || b || c))
*/
public class MultipleCountProjection extends AggregateProjection {

   private boolean distinct;

   protected MultipleCountProjection(String prop) {
      super("count", prop);
   }

   public String toString() {
      if(distinct) {
         return "distinct " + super.toString();
      } else {
         return super.toString();
      }
   }

   public Type[] getTypes(Criteria criteria, CriteriaQuery criteriaQuery) 
   throws HibernateException {
      return new Type[] { Hibernate.INTEGER };
   }

   public String toSqlString(Criteria criteria, int position, CriteriaQuery criteriaQuery) 
   throws HibernateException {
      StringBuffer buf = new StringBuffer();
      buf.append("count(");
      if (distinct) buf.append("distinct ");
        String[] properties = propertyName.split(";");
        for (int i = 0; i < properties.length; i++) {
           buf.append( criteriaQuery.getColumn(criteria, properties[i]) );
             if(i != properties.length - 1) 
                buf.append(" || ");
        }
        buf.append(") as y");
        buf.append(position);
        buf.append('_');
        return buf.toString();
   }

   public MultipleCountProjection setDistinct() {
      distinct = true;
      return this;
   }

}

ExtraProjection.java

package org.hibernate.criterion; 

public final class ExtraProjections
{ 
    public static MultipleCountProjection countMultipleDistinct(String propertyNames) {
        return new MultipleCountProjection(propertyNames).setDistinct();
    }
}

Пример использования:

String propertyNames = "titleName;titleDescr;titleVersion"

criteria countCriteria = ....

countCriteria.setProjection(ExtraProjections.countMultipleDistinct(propertyNames);

Ссылка на https://forum.hibernate.org/viewtopic.php?t=964506

person Yashpal Singla    schedule 08.01.2013

NullPointerException в некоторых случаях! Без criteria.setProjection(Projections.distinct(Projections.property("id"))) все запросы идут хорошо! Это плохое решение!

Другой способ - использовать SQLQuery. В моем случае следующий код работает нормально:

List result = getSession().createSQLQuery(
"SELECT distinct u.id as usrId, b.currentBillingAccountType as oldUser_type,"
+ " r.accountTypeWhenRegister as newUser_type, count(r.accountTypeWhenRegister) as numOfRegUsers"
+ " FROM recommendations r, users u, billing_accounts b WHERE "
+ " r.user_fk = u.id and"
+ " b.user_fk = u.id and"
+ " r.activated = true and"
+ " r.audit_CD > :monthAgo and"
+ " r.bonusExceeded is null and"
+ " group by u.id, r.accountTypeWhenRegister")
.addScalar("usrId", Hibernate.LONG)
.addScalar("oldUser_type", Hibernate.INTEGER)
.addScalar("newUser_type", Hibernate.INTEGER)
.addScalar("numOfRegUsers", Hibernate.BIG_INTEGER)
.setParameter("monthAgo", monthAgo)
.setMaxResults(20)
.list();

Различие делается в базе данных! Напротив:

criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);

где различие делается в памяти, после загрузки сущностей!

person Krzysztof Barczyński    schedule 19.05.2010