76

我正在尝试在连接到其他表的 Hibernate Criteria 查询上使用基于行的限制(例如:setFirstResult(5)和)来实现分页。setMaxResults(10)

可以理解的是,数据被随机截断。其原因在这里解释。

作为一种解决方案,该页面建议使用“第二个 sql 选择”而不是连接。

如何将现有的条件查询(使用 连接createAlias())转换为使用嵌套选择?

4

10 回答 10

107

您可以通过请求不同的 id 列表而不是不同的水合对象列表来获得所需的结果。

只需将其添加到您的标准中:

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

现在,您将根据基于行的限制获得正确数量的结果。之所以可行,是因为投影将作为 sql 查询的一部分执行区别性检查,而不是 ResultTransformer在执行 sql 查询过滤结果的区别性。

值得注意的是,您现在将获得一个 id 列表,而不是获取对象列表,您可以使用它来在以后从休眠状态中对对象进行水合。

于 2008-11-19T00:53:36.903 回答
43

我在我的代码中使用这个。

只需将其添加到您的标准中:

标准.setResultTransformer(标准.DISTINCT_ROOT_ENTITY);

该代码将类似于从本机 sql 的表中选择不同的 *。希望这个有帮助。

于 2009-12-03T06:56:52.433 回答
29

基于 FishBoy 的建议略有改进。

可以一次完成这种查询,而不是分两个单独的阶段。即下面的单个查询将正确分页不同的结果,并且还返回实体而不仅仅是ID。

只需使用带有 id 投影的 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();
于 2011-10-27T04:43:57.447 回答
6

@FishBoy 建议的一个小改进是使用 id 投影,因此您不必对标识符属性名称进行硬编码。

criteria.setProjection(Projections.distinct(Projections.id()));
于 2011-06-08T15:04:32.077 回答
5

解决方案:

criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);

效果很好。

于 2010-04-16T11:07:31.047 回答
4
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

于 2013-01-24T13:12:50.603 回答
3

如果您想使用 ORDER BY,只需添加:

criteria.setProjection(
    Projections.distinct(
        Projections.projectionList()
        .add(Projections.id())
        .add(Projections.property("the property that you want to ordered by"))
    )
);
于 2014-07-14T16:01:59.207 回答
1

我现在将解释一个不同的解决方案,您可以在其中使用正常的查询和分页方法,而不会出现可能重复或抑制项目的问题。

该解决方案具有以下优点:

  • 比本文提到的PK id解决方案更快
  • 保留排序,不要在可能较大的 PK 数据集上使用“in 子句”

完整的文章可以在我的博客上找到

Hibernate 不仅可以在设计时定义关联获取方法,还可以在运行时通过查询执行来定义关联获取方法。因此,我们将这种方法与一个简单的 relfection 东西结合使用,并且还可以自动更改仅针对集合属性的查询属性获取算法的过程。

首先,我们创建一个从实体类解析所有集合属性的方法:

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 不同。因此,您可以在 UI 中对分页算法使用正常的连接关联获取,因为这在大多数情况下不是关键部分,并且尽可能快地获得结果更为重要。

于 2012-07-06T14:44:45.617 回答
0

下面是我们可以通过多重投影来执行 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

于 2013-01-08T07:21:11.380 回答
-1

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

加载实体后,在内存中进行区分!

于 2010-05-19T09:46:14.810 回答