web-dev-qa-db-fra.com

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

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 comportant des jointures avec d'autres tables.

Naturellement, les données sont coupées au hasard; et la raison en est expliquée ici .

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 a des jointures à l'aide de createAlias()) pour utiliser une sélection imbriquée?

69
Daniel Alexiuc

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 exécute le contrôle de distinction dans le cadre de la requête SQL, au lieu de ce que fait un ResultTransformer qui consiste à filtrer les résultats pour la distinction après la 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.

102
FishBoy

J'utilise celui-ci avec mes codes.

Ajoutez simplement ceci à vos critères:

criteres.setResultTransformer (Criteria.DISTINCT_ROOT_ENTITY);

ce code sera comme le select * distinct de la table du SQL natif. J'espère que ça aide.

43
grayshop

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

Il est possible de faire 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 Critères principal.

Cela ressemblera à 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();
26
Daniel Alexiuc

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

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

Cela m'a aidé: D

4
Andrew

La solution:

criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);

fonctionne très bien.

4
JJ.

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

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 des éléments éventuellement dupliqués ou supprimés.

Cette solution a l’avance qu’elle est:

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

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

Hibernate donne 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 un processus 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.

Tout d'abord, nous créons 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;
}

Cela fait, vous pouvez utiliser cette petite méthode d'assistance et demandez à votre critère 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();

Faire cela est différent de définir le FetchMode de vos entités au moment de la conception. Vous pouvez donc utiliser l'extraction d'association de jointure normale sur des algorithmes de pagination dans votre interface utilisateur, car ce n'est généralement pas la partie critique et il est plus important que vos résultats soient aussi rapides que possible.

NullPointerException dans certains cas! Sans criteria.setProjection(Projections.distinct(Projections.property("id"))), toute la requête se passe 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! En face de:

criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);

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

0

Ci-dessous se trouve la manière dont nous pouvons faire la projection multiple pour effectuer des tâches distinctes.

    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é depuis https://forum.hibernate.org/viewtopic.php?t=964506

0
Yashpal Singla