¿Cómo obtener resultados distintos en hibernación con combinaciones y límites basados ​​en filas (paginación)?

Estoy intentando implementar la paginación usando la limitación basada en filas (por ejemplo: setFirstResult(5) y setMaxResults(10) ) en una consulta de Hibernate Criteria que tiene combinaciones con otras tablas.

Comprensiblemente, los datos se cortan 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 combinaciones usando createAlias() ) para usar una selección anidada en su lugar?

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

Simplemente agregue esto a su criterio:

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

Ahora obtendrá la cantidad correcta de resultados según su límite basado en filas. La razón por la que esto funciona es porque la proyección realizará la comprobación de la distinción como parte de la consulta sql, en lugar de lo que hace un ResultTransformer, que es filtrar los resultados por la distinción después de que se haya 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 hibernación más adelante.

Estoy usando este con mis códigos.

Simplemente agregue esto a su criterio:

criteria.setResultTransformer (Criteria.DISTINCT_ROOT_ENTITY);

ese código será como el select distinct * de la tabla del sql nativo. Espero que esto ayude.

Una ligera mejora basada en la sugerencia de FishBoy.

Es posible hacer este tipo de consulta de un golpe, en lugar de hacerlo en dos etapas separadas. es decir, la única consulta a continuación publicará resultados distintos correctamente y también devolverá entidades en lugar de solo ID.

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

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

Una pequeña mejora a la sugerencia de @ FishBoy es usar la proyección de identificación, por lo que no es necesario codificar el nombre de la propiedad del identificador.

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

Esto me ayudó: D

La solución:

 criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY); 

funciona muy bien

si quieres usar ORDER BY, simplemente agrega:

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

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

Esta solución tiene el avance que es:

  • más rápido que la solución de identificación de PK mencionada en este artículo
  • conserva el orden y no utiliza la ‘cláusula in’ en un conjunto de datos posiblemente grande de PK

El artículo completo se puede encontrar en mi blog

Hibernate ofrece la posibilidad de definir el método de búsqueda de asociación no solo en el momento del diseño sino también en el tiempo de ejecución mediante la ejecución de una consulta. Por lo tanto, utilizamos este enfoque junto con un material de relfection simple y también podemos automatizar el proceso de cambiar el algoritmo de obtención de propiedades de la consulta solo para las propiedades de la colección.

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

 public static List resolveCollectionProperties(Class< ?> type) { List ret = new ArrayList(); 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 auxiliar, asesorar a su objeto de criterio para cambiar el Modo de captura para 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 el momento del diseño. Por lo tanto, puede usar la asociación de unión normal que se obtiene en los algoritmos de búsqueda en su IU, porque la mayoría de las veces no es la parte crítica y es más importante que los resultados sean lo más rápidos posible.

A continuación se muestra la forma en que podemos hacer Proyección múltiple para realizar 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(); } } 

Uso de la muestra:

 String propertyNames = "titleName;titleDescr;titleVersion" criteria countCriteria = .... countCriteria.setProjection(ExtraProjections.countMultipleDistinct(propertyNames); 

Remitido de https://forum.hibernate.org/viewtopic.php?t=964506

NullPointerException en algunos casos! Sin criteria.setProjection(Projections.distinct(Projections.property("id"))) todas las consultas van 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 oposición a:

 criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY); 

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