¿Cómo obtener resultados distintos en hibernación con uniones y limitación basada en filas (paginación)?


Estoy tratando de implementar la paginación usando la limitación basada en filas (por ejemplo: setFirstResult(5) y setMaxResults(10)) en una consulta de criterios de Hibernación que tiene uniones a otras tablas.

Comprensiblemente, los datos se están cortando aleatoriamente; y la razón de eso se explica aquí.

Como solución, la página sugiere usar un "segundo sql select" en lugar de un join.

¿Cómo puedo convertir mi consulta de criterios existente (que tiene uniones usando createAlias()) para usar una selección anidada en su lugar?

Author: Subodh Joshi, 2008-11-19

10 answers

Puede lograr el resultado deseado solicitando una lista de identificadores distintos en lugar de una lista de objetos hidratados distintos.

Simplemente agregue esto a su criterio:

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

Ahora obtendrá el número correcto de resultados de acuerdo con su límite basado en filas. La razón por la que esto funciona es porque la proyección realizará la comprobación de distinción como parte de la consulta sql, en lugar de lo que hace un transformadordesultado que es filtrar los resultados para la distinción después de se ha realizado la consulta sql.

Vale la pena señalar que en lugar de obtener una lista de objetos, ahora obtendrá una lista de identificadores, que puede usar para hidratar objetos de hibernar más tarde.

 101
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-11-19 00:53:36

Estoy usando este con mis códigos.

Simplemente agregue esto a su criterio:

Criterios.setResultTransformer (Criteria.DISTINCT_ROOT_ENTITY);

Ese código será como la tabla select distinct * from del sql nativo. Espero que esto ayude.

 43
Author: grayshop,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-12-03 06:56:52

Una ligera mejora basada en la sugerencia de FishBoy.

Es posible hacer este tipo de consulta en una sola visita, en lugar de en dos etapas separadas. es decir, la única consulta a continuación mostrará los distintos resultados correctamente, y también devolverá entidades en lugar de solo IDs.

Simplemente use un DetachedCriteria con una proyección de id como subconsulta, y luego agregue valores de paginación en el objeto main Criteria.

Se verá algo como esto:

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();
 24
Author: Daniel Alexiuc,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-10-27 04:43:57

Una pequeña mejora a la sugerencia de @FishBoy es usar la proyección id, para que no tenga que codificar el nombre de la propiedad identifier.

criteria.setProjection(Projections.distinct(Projections.id()));
 6
Author: nikita,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-06-08 15:04:32
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));

Esto me ayudó: D

 4
Author: Andrew,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-01-24 13:12:50

La solución:

criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);

Funciona muy bien.

 4
Author: JJ.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-12-16 10:52:34

Si desea utilizar ORDER BY, simplemente agregue:

criteria.setProjection(
    Projections.distinct(
        Projections.projectionList()
        .add(Projections.id())
        .add(Projections.property("the property that you want to ordered by"))
    )
);
 2
Author: rekinyz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-07-14 16:01:59

Ahora explicaré una solución diferente, donde puede usar el método normal de consulta y paginación sin tener el problema de posibles duplicados o elementos suprimidos.

Esta Solución tiene el avance que es:

  • más rápido que la solución PK id mencionada en este artículo
  • conserva el Orden y no utilice la cláusula ' in ' en un conjunto de datos posiblemente grande de PK

El Artículo completo se puede encontrar en mi blog

Hibernar da la posibilidad de definir el método de obtención de asociación no solo en tiempo de diseño, sino también en tiempo de ejecución mediante una ejecución de consulta. Así que usamos este enfoque junto con un simple material de relfección y también podemos automatizar el proceso de cambiar el algoritmo de búsqueda de propiedades de consulta solo para propiedades de colección.

Primero creamos un método que resuelve todas las propiedades de la colección de la Clase Entity:

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

Después de hacer eso, puede usar este pequeño método de ayuda. su objeto criteria para cambiar el FetchMode a SELECCIONAR en esa consulta.

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

Hacer eso es diferente de definir el FetchMode de sus entidades en tiempo de diseño. Por lo tanto, puede usar la asociación de unión normal para obtener algoritmos de paginación en su interfaz de usuario, porque la mayoría de las veces esta no es la parte crítica y es más importante tener sus resultados lo más rápido posible.

 1
Author: Andreas Hartmann-schneevoigt,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-07-06 14:44:45

A continuación se muestra la forma en que podemos hacer proyección múltiple para realizar

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

}

Extraproyecciones.java

package org.hibernate.criterion; 

public final class ExtraProjections
{ 
    public static MultipleCountProjection countMultipleDistinct(String propertyNames) {
        return new MultipleCountProjection(propertyNames).setDistinct();
    }
}

Uso de la muestra:

String propertyNames = "titleName;titleDescr;titleVersion"

criteria countCriteria = ....

countCriteria.setProjection(ExtraProjections.countMultipleDistinct(propertyNames);

Referenciado desde https://forum.hibernate.org/viewtopic.php?t=964506

 0
Author: Yashpal Singla,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-01-08 07:21:11

NullPointerException en algunos casos! Sin criteria.setProjection(Projections.distinct(Projections.property("id"))) toda la consulta va bien! Esta solución es mala!

Otra forma es usar SqlQuery. En mi caso el siguiente código funciona 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 distinción se hace en la base de datos! En opuesto a:

criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);

Donde la distinción se hace en memoria, después de cargar entidades!

 0
Author: Krzysztof Barczyński,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-11-20 16:23:32