Wie kann man mit Verknüpfungen und zeilenbasiertem Einschränken (Paging) eindeutige Ergebnisse im Ruhezustand erzielen?

72

Ich versuche, Paging mithilfe einer zeilenbasierten Begrenzung (zum Beispiel: setFirstResult(5)und setMaxResults(10)) für eine Hibernate Criteria-Abfrage zu implementieren , die Verknüpfungen zu anderen Tabellen aufweist.

Verständlicherweise werden Daten zufällig abgeschnitten. und der Grund dafür wird hier erklärt .

Als Lösung wird auf der Seite vorgeschlagen, anstelle eines Joins eine "zweite SQL-Auswahl" zu verwenden.

Wie kann ich meine vorhandene Kriterienabfrage (für die Verknüpfungen verwendet werden createAlias()) konvertieren, um stattdessen eine verschachtelte Auswahl zu verwenden?

Daniel Alexiuc
quelle

Antworten:

106

Sie können das gewünschte Ergebnis erzielen, indem Sie eine Liste unterschiedlicher IDs anstelle einer Liste unterschiedlicher hydratisierter Objekte anfordern.

Fügen Sie dies einfach Ihren Kriterien hinzu:

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

Jetzt erhalten Sie die richtige Anzahl von Ergebnissen entsprechend Ihrer zeilenbasierten Begrenzung. Der Grund dafür ist, dass die Projektion die Unterscheidbarkeitsprüfung als Teil der SQL-Abfrage durchführt, anstatt dass ein ResultTransformer die Ergebnisse nach der SQL-Abfrage auf Unterscheidbarkeit filtert.

Bemerkenswert ist, dass Sie anstelle einer Liste von Objekten jetzt eine Liste von IDs erhalten, mit denen Sie Objekte später aus dem Ruhezustand hydratisieren können.


quelle
Ich erhalte eine Fehlermeldung, wenn ich dies zu meiner DetachedCriteria hinzufüge. "Suche nicht möglich [SQL: SQL nicht verfügbar]." Haben Sie eine Idee
Barbaros Alp
Funktioniert gut für mich - vielleicht überprüfen Sie, ob Sie tatsächlich eine ID namens "id" haben
Daniel Alexiuc
5
FishBoy bin eigentlich ich. Im Jahr 2008 durften Sie Ihre eigenen Fragen nicht beantworten.
Daniel Alexiuc
4
Wie hydratisieren Sie die Objekte später?
Gavin Haynes
1
Aber es gibt nur die ausgewählte Eigenschaft an Sie zurück, zum Beispiel-> id.exampleA. Es gibt nur die Liste mit den Werten von exampleA zurück, nicht mit der Klasse. Verstehen Sie, was ich zu sagen versuche? Wie kannst du dafür sorgen, dass es zur Klasse zurückkehrt? danke
Alberto Acuña
44

Ich benutze diesen mit meinen Codes.

Fügen Sie dies einfach Ihren Kriterien hinzu:

Kriterien.setResultTransformer (Criteria.DISTINCT_ROOT_ENTITY);

Dieser Code entspricht der Auswahl, die sich von der Tabelle des nativen SQL unterscheidet. Hoffe das hilft.

Graustufen
quelle
13
Dies funktioniert in diesem Fall nicht - siehe die Antwort von FishBoy, in der erklärt wird, warum.
Daniel Alexiuc
2
Laut dem Link, den Daniel Alexiuc in seiner Frage bereitgestellt hat, wird dies in nativem SQL nicht immer in eine eindeutige Klausel übersetzt. Aber es funktioniert, wenn Sie nicht paginieren müssen.
Alberto de Paola
4
Da diese Antwort sowohl im Kontext dieser Frage als auch in Bezug auf ihren Inhalt einfach falsch ist, wie hier erläutert [ stackoverflow.com/questions/25536868/… wird diese "Unterscheidung" über ResultSetTransformer durchgeführt, nachdem die Abfrage ausgeführt wurde
user2039709
2
Einfach falsche Antwort, funktioniert nicht mit Ergebnislimit, Leute, die upvoted waren, brauchten kein Limit
che javara
29

Eine leichte Verbesserung auf der Grundlage von FishBoys Vorschlag.

Es ist möglich, diese Art der Abfrage in einem Treffer und nicht in zwei getrennten Schritten durchzuführen. Das heißt, die einzelne Abfrage unten zeigt unterschiedliche Ergebnisse korrekt an und gibt auch Entitäten anstelle von nur IDs zurück.

Verwenden Sie einfach eine DetachedCriteria mit einer ID-Projektion als Unterabfrage und fügen Sie dann Paging-Werte zum Hauptkriterienobjekt hinzu.

Es wird ungefähr so ​​aussehen:

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();
Daniel Alexiuc
quelle
1
Ich denke, diese Antwort ist viel vollständiger und füllt wirklich eine Antwort darüber aus, wie man eine bestimmte Liste eines zugeordneten Objekts hydratisiert. Genau das habe ich gesucht. Vielen Dank. Wirklich, ich denke das ist die beste Antwort.
JamesD
7
Versuchte dies. Funktioniert nicht Die Unterabfrage funktioniert, aber die Hauptabfrage ist immer noch nicht durch "eindeutig" eingeschränkt.
Gary Kephart
Durch diese Antwort habe ich viel Zeit gespart. Vielen Dank.
Rodrigo Almeida
Das funktioniert gut, aber eine Folgefrage: Wie erhalte ich die Gesamtergebnisgröße für die idsOnlyCriteria? Beim Paging möchten Sie häufig wissen, wie viele Seiten / iterms insgesamt vorhanden sind.
Casey
Ich kann überprüfen, ob dies nach dem Testen nicht funktioniert. Wir werden weiterhin Duplikate in der Kriterienabfrage ziehen, wodurch die Paginierung / das Limit durcheinander gebracht wird.
Che Javara
6

Eine kleine Verbesserung des Vorschlags von @ FishBoy ist die Verwendung der ID-Projektion, sodass Sie den Namen der ID-Eigenschaft nicht fest codieren müssen.

criteria.setProjection(Projections.distinct(Projections.id()));
Nikita
quelle
5

Die Lösung:

criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);

funktioniert sehr gut.

JJ.
quelle
5
Das funktioniert gut für normale Abfragen. Diese Frage bezieht sich jedoch speziell auf Hibernate-Abfragen, die "zeilenbasierte Begrenzung" oder "Paging" verwenden.
Daniel Alexiuc
... und das hat Verbindungen zu anderen Tabellen.
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));

Das hat mir geholfen: D.

Andrew
quelle
2

Wenn Sie ORDER BY verwenden möchten, fügen Sie einfach Folgendes hinzu:

criteria.setProjection(
    Projections.distinct(
        Projections.projectionList()
        .add(Projections.id())
        .add(Projections.property("the property that you want to ordered by"))
    )
);
rekinyz
quelle
Könnten Sie bitte erläutern, warum dies funktionieren würde? und wie würde ich die Reihenfolge in mehreren Spalten festlegen und aufsteigend oder absteigend hinzufügen?
Stoppal
1

Ich werde jetzt eine andere Lösung erläutern, bei der Sie die normale Abfrage- und Paginierungsmethode verwenden können, ohne das Problem möglicherweise doppelter oder unterdrückter Elemente zu haben.

Diese Lösung hat den Fortschritt, dass es ist:

  • schneller als die in diesem Artikel erwähnte PK-ID-Lösung
  • behält die Reihenfolge bei und verwendet die 'in-Klausel' nicht für einen möglicherweise großen Datensatz von PKs

Den vollständigen Artikel finden Sie in meinem Blog

Der Ruhezustand bietet die Möglichkeit, die Methode zum Abrufen von Zuordnungen nicht nur zur Entwurfszeit, sondern auch zur Laufzeit durch eine Abfrageausführung zu definieren. Daher verwenden wir diesen Ansatz in Verbindung mit einem einfachen Relfektionsmaterial und können auch den Prozess des Änderns des Abrufalgorithmus für Abfrageeigenschaften nur für Sammlungseigenschaften automatisieren.

Zuerst erstellen wir eine Methode, die alle Sammlungseigenschaften aus der Entitätsklasse auflöst:

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

Anschließend können Sie mit dieser kleinen Hilfsmethode Ihrem Kriterienobjekt empfehlen, den FetchMode für diese Abfrage in SELECT zu ändern.

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

Dies unterscheidet sich von der Definition des FetchMode Ihrer Entitäten zur Entwurfszeit. Sie können also das normale Abrufen von Verknüpfungszuordnungen für Paging-Algorithmen in Ihrer Benutzeroberfläche verwenden, da dies meistens nicht der kritische Teil ist und es wichtiger ist, dass Ihre Ergebnisse so schnell wie möglich vorliegen.

Andreas Hartmann-schneevoigt
quelle
Auf diese Weise werden die Sammlungen nach dem Schließen der Sitzung nicht ausgefüllt, um das Ergebnis der Kriterien zu trennen.
Antgar9
0

Im Folgenden sehen Sie, wie Sie eine Mehrfachprojektion durchführen können, um Distinct auszuführen

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

Beispielnutzung:

String propertyNames = "titleName;titleDescr;titleVersion"

criteria countCriteria = ....

countCriteria.setProjection(ExtraProjections.countMultipleDistinct(propertyNames);

Referenziert von https://forum.hibernate.org/viewtopic.php?t=964506

Yashpal Singla
quelle
0

NullPointerExceptionin manchen Fällen! Ohne criteria.setProjection(Projections.distinct(Projections.property("id"))) alle Fragen geht es gut! Diese Lösung ist schlecht!

Eine andere Möglichkeit ist die Verwendung von SQLQuery. In meinem Fall funktioniert der folgende Code einwandfrei:

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

Die Unterscheidung erfolgt in der Datenbank! Im Gegensatz zu:

criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);

wo die Unterscheidung im Speicher erfolgt, nach dem Laden von Entitäten!

Krzysztof Barczyński
quelle