他のテーブルに結合するHibernate Criteriaクエリで、行ベースの制限(たとえば、setFirstResult(5)
およびsetMaxResults(10)
)を使用してページングを実装しようとしています。
当然、データはランダムに切断されています。その理由は here で説明されています。
解決策として、このページでは、結合の代わりに「2番目のsql select」を使用することを提案しています。
既存の条件クエリ(createAlias()
を使用した結合を含む)を変換して、代わりにネストされた選択を使用するにはどうすればよいですか?
私は自分のコードでこれを使用しています。
これを基準に追加するだけです:
criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
そのコードは、ネイティブSQLのテーブルからselect distinct *のようになります。これが役立つことを願っています。
FishBoyの提案に基づいたわずかな改善。
この種のクエリは、2つの別々の段階ではなく、1回のヒットで実行できます。つまり、以下の単一のクエリは個別の結果を正しくページングし、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"))
)
);
次に、別のソリューションについて説明します。このソリューションでは、アイテムの重複や抑制の問題を抱えることなく通常のクエリとページネーションの方法を使用できます。
このソリューションには次のような利点があります。
完全な記事は my blog にあります。
Hibernateは、設計時だけでなく、実行時のクエリ実行による関連付けフェッチ方法を定義する可能性を提供します。したがって、このアプローチを単純なリレーション機能と組み合わせて使用し、コレクションプロパティに対してのみクエリプロパティフェッチアルゴリズムを変更するプロセスを自動化することもできます。
最初に、Entityクラスのすべてのコレクションプロパティを解決するメソッドを作成します。
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のページングアルゴリズムで通常の結合関連付けフェッチを使用できます。これはほとんどの場合、重要な部分ではなく、可能な限り迅速に結果を取得することがより重要だからです。
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);
エンティティをロードした後、メモリ内で区別が行われます!
以下は、複数の投影を実行して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);