Comment obtenir des résultats distincts en veille prolongée avec des jointures et une limitation basée sur les lignes (pagination)?

StackOverflow https://stackoverflow.com/questions/300491

Question

J'essaie d'implémenter la pagination à l'aide d'une limitation basée sur les lignes (par exemple: setFirstResult (5) et setMaxResults (10) ) sur une requête Hibernate Criteria ayant se joint à d'autres tables.

Naturellement, les données sont coupées de manière aléatoire; et la raison est la suivante.

En guise de solution, la page suggère d'utiliser un "deuxième sql select". au lieu d'une jointure.

Comment puis-je convertir ma requête de critères existante (qui comporte des jointures à l'aide de createAlias ??() ) pour utiliser une sélection imbriquée?

Était-ce utile?

La solution

Vous pouvez obtenir le résultat souhaité en demandant une liste d'identifiants distincts au lieu d'une liste d'objets hydratés distincts.

Ajoutez simplement ceci à vos critères:

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

Vous obtiendrez maintenant le nombre correct de résultats en fonction de votre limitation basée sur les lignes. Cela s'explique par le fait que la projection effectue la vérification de la distinction dans le cadre de la requête SQL, au lieu de ce que ResultTransformer effectue: le filtrage des résultats pour la distinction après . La requête SQL a été effectuée.

Il est à noter que, au lieu d’obtenir une liste d’objets, vous obtiendrez maintenant une liste d’identifiants que vous pourrez utiliser pour hydrater ultérieurement des objets en veille prolongée.

Autres conseils

J'utilise celui-ci avec mes codes.

Ajoutez simplement ceci à vos critères:

  

criteres.setResultTransformer (Critères.DISTINCT_ROOT_ENTITY);

ce code sera comme le select * distinct de la table du SQL natif. J'espère que cela vous aidera.

Une légère amélioration s'appuyant sur la suggestion de FishBoy.

Il est possible d'effectuer ce type de requête en un seul coup, plutôt qu'en deux étapes distinctes. c'est-à-dire que la requête unique ci-dessous recherche correctement les résultats distincts et renvoie également des entités plutôt que des identifiants.

Utilisez simplement un DetachedCriteria avec une projection id en tant que sous-requête, puis ajoutez des valeurs de pagination sur l'objet Criteria principal.

Cela ressemblera à quelque chose comme ceci:

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

Une petite amélioration à la suggestion de @ FishBoy est d'utiliser la projection id, de sorte que vous n'ayez pas à coder en dur le nom de la propriété de l'identifiant.

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

Cela m'a aidé: D

La solution:

criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);

fonctionne très bien.

si vous souhaitez utiliser ORDER BY, ajoutez simplement:

criteria.setProjection(
    Projections.distinct(
        Projections.projectionList()
        .add(Projections.id())
        .add(Projections.property("the property that you want to ordered by"))
    )
);

Je vais maintenant expliquer une solution différente, dans laquelle vous pouvez utiliser la méthode de requête et de pagination normale sans le problème de la possibilité de dupliquer ou de supprimer des éléments.

Cette solution a le même avantage:

  • plus rapide que la solution d’identifiant PK mentionnée dans cet article
  • conserve la commande et n’utilise pas la clause 'in' sur un jeu de données éventuellement volumineux de PK

L’article complet se trouve sur mon blog

Hibernate offre la possibilité de définir la méthode de récupération d’association non seulement au moment de la conception, mais également au moment de l’exécution par une requête. Nous utilisons donc cette approche en conjonction avec une procédure de réinfection simple et pouvons également automatiser le processus de modification de l'algorithme d'extraction de propriété de requête uniquement pour les propriétés de collection.

Nous créons d'abord une méthode qui résout toutes les propriétés de collection de la classe d'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;
}

Après cela, vous pouvez utiliser cette petite méthode d'assistance, conseillez à votre objet de critères de changer le FetchMode en SELECT sur cette requête.

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

Cela diffère de la définition du FetchMode de vos entités au moment de la conception. Vous pouvez donc utiliser la récupération normale d'association de jointure sur des algorithmes de pagination dans votre interface utilisateur, car ce n'est généralement pas la partie critique et qu'il est plus important que vos résultats soient aussi rapides que possible.

Ci-dessous, la manière dont nous pouvons faire la projection multiple pour effectuer 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();
    }
}

Exemple d'utilisation:

String propertyNames = "titleName;titleDescr;titleVersion"

criteria countCriteria = ....

countCriteria.setProjection(ExtraProjections.countMultipleDistinct(propertyNames);

Référencé à partir de https://forum.hibernate.org/viewtopic.php?t= 964506

NullPointerException dans certains cas! Sans criteres.setProjection (Projections.distinct (Projections.property (" id "))) toute requête va bien! Cette solution est mauvaise!

Une autre façon consiste à utiliser SQLQuery. Dans mon cas, le code suivant fonctionne bien:

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 distinction est faite dans la base de données! Contrairement à:

criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);

où la distinction est faite en mémoire, après le chargement des entités!

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top