Como obter resultados distintos em hibernação com junções e baseada em linha limite (paginação)?

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

Pergunta

Eu estou tentando implementar a paginação usando limitar baseado em linha. (Por exemplo: setFirstResult(5) e setMaxResults(10)) em uma consulta Critérios Hibernate que tem se junta a outras tabelas

Compreensivelmente, os dados está ficando cut off aleatoriamente; e a razão para isso é explicado aqui .

Como uma solução, a página sugere o uso de uma "segunda SQL SELECT" em vez de uma junção.

Como posso converter minha consulta critérios existente (que tem se junta usando createAlias()) para usar um SELECT aninhada em vez disso?

Foi útil?

Solução

Você pode obter o resultado desejado, solicitando uma lista de IDs distintos em vez de uma lista de objetos hidratados distintos.

Simplesmente adicione aos seus critérios:

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

Agora você vai ter o número correto de resultados de acordo com a sua linha-base limitante. A razão isto funciona é porque a projeção vai realizar a verificação distinção como parte a consulta SQL, em vez do que um ResultTransformer se que é para filtrar os resultados para distinção depois o foi realizada consulta SQL.

Digno de nota é que, em vez de obter uma lista de objetos, agora você vai ter uma lista de ids, que você pode usar para hidratar os objetos do modo de hibernação mais tarde.

Outras dicas

Eu estou usando este com meus códigos.

Simplesmente adicione aos seus critérios:

criteria.setResultTransformer (Criteria.DISTINCT_ROOT_ENTITY);

esse código será como o select * distinta da tabela do SQL nativo. Espero que esta ajuda.

A ligeira melhoria com base na sugestão de FishBoy.

É possível fazer este tipo de consulta em uma batida, em vez de em duas etapas separadas. ou seja, a única consulta abaixo página vontade resultados distintos corretamente, e também retornam entidades em vez de apenas IDs.

Basta usar um DetachedCriteria com uma projeção id como uma subconsulta, e depois adicionar valores de paginação no objeto critérios principais.

Ele será parecido com isto:

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

Uma pequena melhoria para @ sugestão de FishBoy é usar a projeção id, para que você não tem que codificar o nome da propriedade 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));

Isso me ajudou: D

A solução:

criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);

funciona muito bem.

Se você quiser usar ORDER BY, basta adicionar:

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

Agora vou explicar uma solução diferente, onde você pode usar a consulta normal e método de paginação sem ter o problema de possivelmente duplicados ou itens suprimidos.

Esta solução tem a antecedência que é:

  • mais rápido do que a solução PK id mencionado neste artigo
  • preserva a ordenação e não use o 'na cláusula' em um possivelmente grande conjunto de dados de PK

O artigo completo pode ser encontrado em meu blog

Hibernate dá a possibilidade de definir o método atraente associação, não só em tempo de design, mas também em tempo de execução por uma execução da consulta. Então, nós usamos este aproach em conjunto com um material relfection simples e também pode automatizar o processo de alterar a propriedade consulta buscar algoritmo apenas para propriedades de coleção.

Primeiro vamos criar um método que resolve todas as propriedades da coleção da classe de entidade:

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

Depois de fazer isso você pode usar este método auxiliar Mal sabem aconselhar os seus critérios de objeto para alterar a FetchMode para SELECT nessa 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();

Fazer isso é diferente de definir o FetchMode de suas entidades em tempo de design. Então você pode usar o join normal associação buscar na paginação algoritmos em você UI, porque esta é a maior parte do tempo não a parte crítica e é mais importante ter os resultados tão rápido quanto possível.

A seguir é a maneira que nós podemos fazer projeção múltipla para executar 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 Amostra:

String propertyNames = "titleName;titleDescr;titleVersion"

criteria countCriteria = ....

countCriteria.setProjection(ExtraProjections.countMultipleDistinct(propertyNames);

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

NullPointerException em alguns casos! sem criteria.setProjection(Projections.distinct(Projections.property("id"))) tudo consulta vai bem! Esta solução é mau!

Outra maneira é usar SQLQuery. No meu caso seguinte código fina funciona:

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

Distinção é feita em base de dados Em frente ao:

criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);

onde distinção é feita em memória, depois de entidades de carga!

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top