Как получить четкие результаты в спящем режиме с помощью объединений и ограничения на основе строк (подкачки)? - PullRequest
68 голосов
/ 19 ноября 2008

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

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

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

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

Ответы [ 10 ]

101 голосов
/ 19 ноября 2008

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

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

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

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

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

43 голосов
/ 03 декабря 2009

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

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

criteria.setResultTransformer (Criteria.DISTINCT_ROOT_ENTITY);

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

26 голосов
/ 27 октября 2011

Небольшое улучшение по предложению FishBoy.

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

Просто используйте DetachedCriteria с проекцией id в качестве подзапроса, а затем добавьте значения подкачки в основной объект 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();
6 голосов
/ 08 июня 2011

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

criteria.setProjection(Projections.distinct(Projections.id()));
4 голосов
/ 24 января 2013
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

4 голосов
/ 16 апреля 2010

Решение:

criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);

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

2 голосов
/ 14 июля 2014

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

criteria.setProjection(
    Projections.distinct(
        Projections.projectionList()
        .add(Projections.id())
        .add(Projections.property("the property that you want to ordered by"))
    )
);
1 голос
/ 06 июля 2012

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

Это решение имеет преимущество:

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

Полная статья может быть найдена на мой блог

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

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

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 ваших сущностей во время разработки. Таким образом, вы можете использовать обычную выборку объединяющих объединений для алгоритмов подкачки в вашем пользовательском интерфейсе, поскольку в большинстве случаев это не критическая часть, и более важно, чтобы ваши результаты были как можно быстрее.

0 голосов
/ 08 января 2013

Ниже приведен способ, которым мы можем сделать несколько проекций для выполнения Distinct

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

}

ExtraProjections.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

0 голосов
/ 19 мая 2010

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

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

...