70 votes

Comment obtenir des résultats distincts dans Hibernate avec des jointures et une limitation par ligne (pagination) ?

J'essaie de mettre en œuvre la pagination en utilisant la limitation par rangée (par exemple : setFirstResult(5) y setMaxResults(10) ) sur une requête Hibernate Criteria qui comporte des jointures avec d'autres tables.

Il est compréhensible que les données soient coupées de manière aléatoire, et la raison en est expliquée. ici .

Comme solution, la page suggère d'utiliser un "second sql select" au lieu d'une jointure.

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

4voto

Werd Points 55
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

3voto

user1146450 Points 2429

Si vous voulez utiliser ORDER BY, il suffit d'ajouter :

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

1voto

Je vais maintenant vous expliquer une autre solution, qui vous permet d'utiliser la méthode normale de recherche et de pagination sans avoir le problème des éventuels doublons ou des éléments supprimés.

Cette solution a l'avance qu'elle a :

  • plus rapide que la solution PK id mentionnée dans cet article
  • préserve l'ordre et n'utilise pas la clause "in" sur un éventuel grand ensemble de données de PK

L'article complet peut être consulté sur mon blog

Hibernate offre la possibilité de définir la méthode de récupération des associations non seulement au moment de la conception, mais aussi au moment de l'exécution d'une requête. Ainsi, nous utilisons cette approche en conjonction avec une simple méthode de récupération et nous pouvons également automatiser le processus de changement de l'algorithme de récupération des propriétés de la requête uniquement pour les propriétés de la collection.

Tout d'abord, nous créons une méthode qui résout toutes les propriétés de la collection à partir 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 avoir fait cela, vous pouvez utiliser cette petite méthode d'aide pour conseiller votre objet de critères afin 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();

Il ne s'agit pas de définir le FetchMode de vos entités au moment de la conception. Ainsi, vous pouvez utiliser les algorithmes normaux de recherche d'association de jointure sur la pagination dans votre interface utilisateur, car la plupart du temps, ce n'est pas la partie critique et il est plus important d'avoir vos résultats aussi rapidement que possible.

0voto

Yashpal Singla Points 647

Voici la façon dont nous pouvons faire une projection multiple pour effectuer une distinction.

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

Utilisation de l'échantillon :

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

-1voto

NullPointerException dans certains cas ! Sans criteria.setProjection(Projections.distinct(Projections.property("id"))) toutes les requêtes se passent bien ! Cette solution est mauvaise !

Une autre façon est d'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 se fait dans la base de données ! En face de :

criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X