如何在连接和基于行的限制(分页)中获得不同的结果?
我试图使用基于行的限制(例如: setFirstResult(5)
和setMaxResults(10)
)对已经连接到其他表的Hibernate Criteria查询实现分页。
可以理解的是,数据随机被切断; 原因在这里解释。
作为解决scheme,页面build议使用“第二个sql select”而不是联接。
如何将我现有的标准查询(使用createAlias()
连接)转换为使用嵌套的select?
您可以通过请求一个不同的ID列表而不是一个独特的水合对象列表来达到预期的效果。
只需将其添加到您的标准:
criteria.setProjection(Projections.distinct(Projections.property("id")));
现在,您将根据基于行的限制获得正确数量的结果。 这个工作的原因是因为投影将执行独立性检查作为 sql查询的一部分 ,而不是ResultTransformer所做的是在sql查询执行后过滤结果以获得独特性。
值得注意的是,不是获取对象列表,现在您将得到一个ID列表,您可以使用它来从稍后的hibernate中提取对象。
我用我的代码使用这个。
只需将其添加到您的标准:
criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
该代码将像本地SQL的表中select不同*。 希望这个帮助。
build立在FishBoy的build议上略有改进。
一次可以做这种查询,而不是分两个阶段。 即下面的单个查询将正确分页不同的结果,并返回实体,而不仅仅是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();
对@ FishBoy的build议的一个小改进是使用id投影,所以你不必硬编码标识符属性名称。
criteria.setProjection(Projections.distinct(Projections.id()));
解决scheme:
criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
工作得很好。
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
现在我将解释一个不同的解决scheme,在这里你可以使用正常的查询和分页方法,而不会产生可能的重复或抑制项目的问题。
该解决scheme具有以下优点:
- 比本文中提到的PK id解决scheme更快
- 保留了Ordering,并且不要在PK的可能大数据集中使用'in clause'
完整的文章可以在我的博客上find
Hibernate不仅可以在devise时定义关联获取方法,而且可以在运行时通过查询执行来定义关联获取方法。 因此,我们使用这个aproach与一个简单的重新工作的东西,也可以自动更改查询属性获取algorithm的过程只为集合属性。
首先我们创build一个parsing实体类的所有集合属性的方法:
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; }
这样做之后,你可以使用这个小帮手方法build议你的标准对象在该查询中将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();
这样做与devise时定义实体的FetchMode不同。 所以你可以在你的UI中使用正常的连接关联获取分页algorithm,因为这大部分时间不是关键部分,并且尽可能快地得到结果更重要。
如果你想使用ORDER BY,只需添加:
criteria.setProjection( Projections.distinct( Projections.projectionList() .add(Projections.id()) .add(Projections.property("the property that you want to ordered by")) ) );
以下是我们可以做多重投影执行不同的方式
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);
NullPointerException
在某些情况下! 如果没有criteria.setProjection(Projections.distinct(Projections.property("id")))
所有查询顺利! 这个解决scheme不好!
另一种方法是使用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);
在内存中完成区分后,加载实体后!