如何在具有联接和基于行的限制(分页)的休眠状态下获得独特的结果?


73

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

可以理解,数据是随机切断的。而其中的原因进行了说明这里

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

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

Answers:


106

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

只需将其添加到您的条件中:

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

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

值得一提的是,您现在将获得一个ID列表,而不是获取对象列表,您可以使用它们在以后休眠的情况下合并对象。


将其添加到DetachedCriteria中时出现错误:“无法执行find [SQL:SQL不可用]”您有任何想法吗
Barbaros Alp,2009年

对我来说效果很好-也许检查一下您是否有一个名为“ id”的ID
Daniel Alexiuc,2009年

5
FishBoy实际上是我。早在08年,您就不允许回答自己的问题。
Daniel Alexiuc 2012年

4
以后如何给对象补水?
加文·海恩斯

1
但是它只返回选择的属性,例如-> id.exampleA,它将仅返回带有exampleA值的列表,而不返回带有类的列表,您是否理解我想说的话?您如何使它返回给您上课?谢谢
AlbertoAcuña17年

44

我将此代码与我的代码一起使用。

只需将其添加到您的条件中:

条件.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);

该代码将类似于从本机sql表中选择select *。希望这对您有所帮助。


13
在这种情况下,这将无法正常工作-请参阅FishBoy的答案,其中解释了原因。
Daniel Alexiuc 09年

2
而且,根据Daniel Alexiuc在他的问题中提供的链接,这并不总是在本机sql中转换为不同的子句。但是,如果您不需要分页,它确实可以工作。
阿尔贝托·德保罗

4
不赞成投票,因为这个答案在这个问题的上下文及其内容上都是错误的,就像这里解释的那样[ stackoverflow.com/questions/25536868/…通过ResultSetTransformer实现的这种“区别”是执行查询完成的
user2039709

2
简单错误的答案,不符合结果限制,投票的人不需要限制
che javara

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

1
我认为这个答案要完整得多,并且确实填写了有关如何合并相关对象的不同列表的答案。这正是我想要的。谢谢。真的,我认为这是最好的答案。
JamesD

7
试过这个。不起作用 子查询有效,但是主查询仍然不受“ distinct”的约束。
2013年

因为这个答案,我节省了很多时间,非常感谢。
罗德里戈·阿尔梅达

这很好用,但是有一个后续问题:如何获得idsOnlyCriteria的总结果大小?通常在分页中,您想知道总共有多少个页面/项。
Casey

我可以验证这在测试后不起作用,我们仍将在条件查询中提取重复项,这会弄乱分页/限制。
che javara

6

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

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

6

解决方案:

criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);

效果很好。


5
对于正常查询来说,这很好用。但是这个问题专门询问有关使用“基于行的限制”或“分页”的Hibernate查询。
Daniel Alexiuc 2010年

...并且已经连接到其他表。
Daniel Alexiuc

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


2

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

criteria.setProjection(
    Projections.distinct(
        Projections.projectionList()
        .add(Projections.id())
        .add(Projections.property("the property that you want to ordered by"))
    )
);

您能否详细说明为什么会这样。以及如何在多个列上设置顺序并添加升序或降序?
Stoppal 2014年

1

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

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

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

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

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

首先,我们创建一个方法来解析Entity Class中的所有集合属性:

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


这样,关闭会话或分离标准结果后,您将不会填充集合。
antgar9 '16

0

以下是我们可以进行多次投影以执行不同效果的方法

    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

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

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.