it-swarm.com.de

Wie erhält man eindeutige Ergebnisse im Ruhezustand mit Joins und zeilenbasierter Begrenzung (Paging)?

Ich versuche, Paging mit zeilenbasierter Einschränkung (zum Beispiel: setFirstResult(5) und setMaxResults(10)) in einer Hibernate Criteria-Abfrage zu implementieren, die Verknüpfungen mit anderen Tabellen aufweist.

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

Als Lösung wird auf der Seite vorgeschlagen, anstelle eines Joins "second sql select" zu verwenden.

Wie kann ich meine vorhandene Kriterienabfrage (die Verknüpfungen mit createAlias() enthält) konvertieren, um stattdessen eine verschachtelte Auswahl zu verwenden?

69
Daniel Alexiuc

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 filtert zur Unterscheidung nachdem die SQL-Abfrage durchgeführt wurde.

Anstatt eine Liste mit Objekten zu erhalten, erhalten Sie jetzt eine Liste mit IDs, mit denen Sie Objekte aus dem Ruhezustand hydrieren können.

102
FishBoy

Ich benutze dieses mit meinen Codes.

Fügen Sie dies einfach Ihren Kriterien hinzu:

criterion.setResultTransformer (Criteria.DISTINCT_ROOT_ENTITY);

dieser Code entspricht der select-Anweisung, die sich von der Tabelle der nativen SQL unterscheidet. Hoffe das hilft.

43
grayshop

Eine leichte Verbesserung, die auf FishBoys Vorschlag aufbaut.

Es ist möglich, diese Art von Abfrage in einem Treffer und nicht in zwei separaten Schritten durchzuführen. d.h. die einzelne Abfrage unten gibt bestimmte Ergebnisse korrekt aus und gibt Entitäten statt nur IDs zurück.

Verwenden Sie einfach ein DetachedCriteria mit einer ID-Projektion als Unterabfrage und fügen Sie dann Paging-Werte zum Hauptobjekt Criteria 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();
26
Daniel Alexiuc

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

criteria.setProjection(Projections.distinct(Projections.id()));
6
nikita
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

4
Andrew

Die Lösung:

criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);

funktioniert sehr gut.

4
JJ.

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"))
    )
);
2
rekinyz

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

Diese Lösung hat den Vorteil, dass es:

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

Den vollständigen Artikel finden Sie auf mein Blog

Hibernate 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 relfection-Zeug und können auch den Prozess des Änderns des Algorithmus zum Abrufen von Abfrageeigenschaften nur für Sammlungseigenschaften automatisieren.

Zuerst erstellen wir eine Methode, die alle Auflistungseigenschaften aus der Entity-Klasse 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;
}

Danach können Sie diese kleine Hilfsmethode verwenden und Ihrem Kriterienobjekt raten, 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 den normalen Verknüpfungsabruf für Paging-Algorithmen in Ihrer Benutzeroberfläche verwenden, da dies in den meisten Fällen nicht der kritische Teil ist und es wichtiger ist, Ihre Ergebnisse so schnell wie möglich zu erhalten.

NullPointerException in einigen Fällen! Ohne criteria.setProjection(Projections.distinct(Projections.property("id"))) geht alle Abfrage gut! Diese Lösung ist schlecht!

Eine andere Möglichkeit ist die Verwendung von SQLQuery. In meinem Fall funktioniert folgender 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();

nterscheidung erfolgt in Datenbank! Im Gegensatz zu:

criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);

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

Unten sehen Sie, wie Sie mehrere Projektionen 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

0
Yashpal Singla