Come ottenere risultati distinti in ibernazione con join e limitazione basata sulla riga (paging)?
Domanda
Sto cercando di implementare il paging usando la limitazione basata su righe (ad esempio: setFirstResult (5)
e setMaxResults (10)
) su una query di criteri di ibernazione che ha si unisce ad altri tavoli.
Comprensibilmente, i dati vengono tagliati casualmente; e la ragione di ciò è spiegata
Soluzione È possibile ottenere il risultato desiderato richiedendo un elenco di ID distinti anziché un elenco di oggetti idratati distinti. Aggiungilo semplicemente ai tuoi criteri: Ora otterrai il numero corretto di risultati in base al limite basato sulle righe. Il motivo per cui funziona è perché la proiezione eseguirà il controllo di distinzione come parte di la query sql, invece di quello che fa un ResultTransformer che è quello di filtrare i risultati per la distinzione dopo il query sql è stata eseguita. Vale la pena notare che invece di ottenere un elenco di oggetti, ora otterrai un elenco di ID, che puoi utilizzare per idratare gli oggetti dal letargo in seguito. criteria.setProjection(Projections.distinct(Projections.property("id")));
Altri suggerimenti
Sto usando questo con i miei codici.
Aggiungilo semplicemente ai tuoi criteri:
criteria.setResultTransformer (Criteria.DISTINCT_ROOT_ENTITY);
quel codice sarà come il distinto selezionare * dalla tabella del sql nativo. Spero che questo aiuti.
Un leggero miglioramento basato sul suggerimento di FishBoy.
È possibile eseguire questo tipo di query in un colpo, piuttosto che in due fasi separate. vale a dire che la singola query seguente mostrerà correttamente risultati distinti e restituirà anche entità anziché solo ID.
È sufficiente utilizzare un DetachedCriteria con una proiezione ID come sottoquery, quindi aggiungere i valori di paging sull'oggetto Criteria principale.
Sarà simile a questo:
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();
Un piccolo miglioramento al suggerimento di @ FishBoy è l'uso della proiezione id, quindi non è necessario codificare il nome della proprietà dell'identificatore.
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));
Questo mi ha aiutato: D
La soluzione:
criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
funziona molto bene.
se vuoi usare ORDER BY, aggiungi semplicemente:
criteria.setProjection(
Projections.distinct(
Projections.projectionList()
.add(Projections.id())
.add(Projections.property("the property that you want to ordered by"))
)
);
Spiegherò ora una soluzione diversa, in cui è possibile utilizzare il normale metodo di query e impaginazione senza avere il problema di eventuali duplicati o elementi eliminati.
Questa soluzione ha il vantaggio di essere:
- più veloce della soluzione ID PK menzionata in questo articolo
- conserva l'ordinamento e non usa la "clausola in" su un set di dati possibilmente grande di PK & # 8217; s
L'articolo completo è disponibile su il mio blog
Hibernate offre la possibilità di definire il metodo di recupero dell'associazione non solo in fase di progettazione, ma anche in fase di esecuzione mediante l'esecuzione di una query. Quindi usiamo questo approccio insieme a una semplice roba di relection e possiamo anche automatizzare il processo di modifica dell'algoritmo di recupero delle proprietà della query solo per le proprietà della raccolta.
Per prima cosa creiamo un metodo che risolve tutte le proprietà della raccolta dalla Classe di entità:
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;
}
Dopo averlo fatto, puoi utilizzare questo piccolo metodo di supporto e consiglia ai tuoi criteri di modificare l'oggetto FetchMode in SELECT su quella query.
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();
Ciò è diverso dal definire la modalità Fetch delle entità in fase di progettazione. Quindi puoi usare il normale recupero delle associazioni di join sugli algoritmi di paging nella tua interfaccia utente, perché questa non è la parte critica, ed è più importante avere i risultati il ??più rapidamente possibile.
Di seguito è riportato il modo in cui possiamo eseguire la proiezione multipla per eseguire 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();
}
}
Utilizzo di esempio:
String propertyNames = "titleName;titleDescr;titleVersion"
criteria countCriteria = ....
countCriteria.setProjection(ExtraProjections.countMultipleDistinct(propertyNames);
Citato da https://forum.hibernate.org/viewtopic.php?t= 964506
NullPointerException
in alcuni casi!
Senza criteri.setProjection (Projections.distinct (Projections.property (" id ")))
tutte le domande vanno bene!
Questa soluzione è pessima!
Un altro modo è usare SQLQuery. Nel mio caso il seguente codice funziona bene:
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();
La distinzione viene fatta nella banca dati! Al contrario di:
criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
dove si fa distinzione in memoria, dopo aver caricato entità!