如何通过连接和基于行的限制(分页)在休眠中获得不同的结果?
题
我正在尝试使用基于行的限制来实现分页(例如: setFirstResult(5)
和 setMaxResults(10)
)在具有与其他表的联接的 Hibernate Criteria 查询上。
可以理解的是,数据被随机切断;并解释了原因 这里.
作为解决方案,该页面建议使用“第二个 sql select”而不是联接。
如何转换现有的条件查询(其中使用连接 createAlias()
)使用嵌套选择代替?
解决方案
您可以通过请求不同的ID列表而不是不同的水合物列表来实现所需的结果。
只需将其添加到您的标准中:
criteria.setProjection(Projections.distinct(Projections.property("id")));
现在,您将根据基于行的限制获得正确数量的结果。这样做的原因是因为投影将执行清晰度检查作为 sql查询的一部分,而不是ResultTransformer用来过滤之后的清晰度的结果 sql查询已经执行。
值得注意的是,您现在将获得一个id列表,而不是获取对象列表,您可以使用它来从以后的hibernate中对象进行水合。
其他提示
我正在使用我的代码。
只需将其添加到您的标准中:
criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
该代码将类似于本机sql的select distinct * from table。希望这个有所帮助。
根据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();
@ FishBoy建议的一个小改进是使用id投影,因此您不必对标识符属性名称进行硬编码。
criteria.setProjection(Projections.distinct(Projections.id()));
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
解决方案:
criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
效果很好。
如果你想使用ORDER BY,只需添加:
criteria.setProjection(
Projections.distinct(
Projections.projectionList()
.add(Projections.id())
.add(Projections.property("the property that you want to ordered by"))
)
);
我现在将解释一个不同的解决方案,您可以使用正常的查询和分页方法,而不会出现可能重复或抑制项目的问题。
该解决方案的优点是:
- 比本文提到的PK id解决方案更快
- 保留排序并且不在可能较大的 PK 数据集上使用“in 子句”
完整的文章可以在以下位置找到 我的博客
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 不同。因此,您可以在 UI 中对分页算法使用正常的连接关联获取,因为这在大多数情况下不是关键部分,更重要的是尽快获得结果。
以下是我们可以执行多项投影以执行区别
的方式 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")))
所有查询都顺利!
这个解决方案很糟糕!
另一种方法是使用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);
在加载实体之后,在内存中进行区分!