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

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

Pregunta

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

Es comprensible que los datos se corten al azar; "."

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

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

¿Fue útil?

Solución

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

Simplemente agregue esto a sus criterios:

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 ResultTransformer que es filtrar los resultados para la distinción después de el consulta SQL ha sido realizada.

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 los objetos de hibernación más adelante.

Otros consejos

Estoy usando este con mis códigos.

Simplemente agregue esto a sus criterios:

  

criterios.setResultTransformer (Criteria.DISTINCT_ROOT_ENTITY);

ese código será como la selección distinta * 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 con un solo golpe, en lugar de hacerlo en dos etapas separadas. es decir, la única consulta a continuación mostrará resultados distintos correctamente y también devolverá entidades en lugar de solo ID.

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

Se verá más o menos así:

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 id, para que no tenga que 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 desea usar ORDER BY, simplemente agregue:

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 utilizar el método normal de consulta y paginación sin tener el problema de posibles elementos duplicados o 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 pedido y no use la cláusula 'in' en un conjunto de datos posiblemente grande de PK & # 8217; s

El artículo completo se puede encontrar en mi blog

Hibernate ofrece 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. Por lo tanto, utilizamos este enfoque junto con un elemento de relfection simple y también podemos automatizar el proceso de cambiar el algoritmo de obtención de propiedades de consulta solo para propiedades de colección.

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

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 auxiliar, aconseje a su objeto de criterio que cambie FetchMode a SELECT 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 búsqueda de asociación de unión normal en algoritmos de paginación en su interfaz de usuario, porque esta no es la parte crítica la mayoría de las veces y es más importante tener sus resultados lo más rápido 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();
    }
}

Ejemplo de uso:

String propertyNames = "titleName;titleDescr;titleVersion"

criteria countCriteria = ....

countCriteria.setProjection(ExtraProjections.countMultipleDistinct(propertyNames);

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

NullPointerException en algunos casos! Sin criterios.setProyección (Projections.distinct (Projections.property (" id "))) toda 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 realiza en la base de datos! Al contrario de:

criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);

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

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top