2012-01-29 27 views
5

Soy nuevo en JPA y quiero implementar un DAO JPA genérico y necesito encontrar el número de filas de un conjunto de resultados de consulta para implementar la paginación. Después de buscar en la web, no puedo encontrar una forma práctica de hacerlo. Aquí está el código sugerido en muchos artículos:¿Cómo contar el número de filas de un JPA 2 CriteriaQuery en un JPA DAO genérico?

public <T> Long findCountByCriteria(CriteriaQuery<?> criteria) { 
    CriteriaBuilder builder = em.getCriteriaBuilder(); 

    CriteriaQuery<Long> countCriteria = builder.createQuery(Long.class); 
    Root<?> entityRoot = countCriteria.from(criteria.getResultType()); 
    countCriteria.select(builder.count(entityRoot)); 
    countCriteria.where(criteria.getRestriction()); 

    return em.createQuery(countCriteria).getSingleResult(); 
} 

Sin embargo, ese código no funciona cuando se utiliza join. ¿Hay alguna forma de contar las filas de un conjunto de resultados de consulta utilizando la API de criterios de JPA?

ACTUALIZACIÓN: Este es el código que crean CriteriaQuery:

CriteriaQuery<T> queryDefinition = criteriaBuilder.createQuery(this.entityClass); 
    Root<T> root = queryDefinition.from(this.entityClass); 

y algunos se une puede ser añadido a la raíz hasta que la consulta se ha ejecutado:

public Predicate addPredicate(Root<T> root) { 
       Predicate predicate = getEntityManager().getCriteriaBuilder().ge(root.join(Entity_.someList).get("id"), 13); 
       return predicate; 
} 

y la excepción generada es como:

org.hibernate.hql.ast.QuerySyntaxException: Ruta no válida: 'generatedAlias1.id' [select count (generatedAlias0) de entity.Entity como generatedAlias0 donde (generatedAlias0.id> = 13L) y ( (generatedAlias1.id < = 34L))]

que generatedAlias1 debe estar en Entity y generatedAlias0 debería estar en la asociación a la que me uní en eso. Tenga en cuenta que implemento Join correctamente porque cuando ejecuto la consulta sin la consulta de recuento se ejecuta sin error y la unión funciona correctamente, pero cuando intento ejecutar la consulta de recuento se lanza una excepción.

+0

¿Puede mostrarnos cómo ha definido CriteriaQuery criterios – perissf

+0

@perissf He actualizado la publicación. – stacker

+0

Vea otras respuestas a problemas similares: http://stackoverflow.com/questions/9001289/jpa-hibernate-criteriabuilder-how-to-create-query-using-relationship-object/9002225#9002225 y http: // stackoverflow.com/questions/9025196/how-to-use-jpa-criteria-api-when-joining-many-tables/9025656#9025656 – perissf

Respuesta

1

No puedo decirle dónde está su problema, pero puedo decirle que las consultas de conteo con combinaciones funcionan bien, al menos en eclipselink jpa. Creo que esto es algo estándar, por lo que debería funcionar también en Hibernate. Comenzaría por simplificar su código para detectar dónde está el problema. Veo que ha copiado algunas partes de su consulta cont de su consulta principal. Tal vez puedas intentar cambiar un poco este enfoque, solo para el propósito de la depuración.

Lo que hago normalmente es:

CriteriaQuery cqCount = builder.createQuery(); 
Root<T> root = cq.from(T.class); 
cqCount.select(builder.count(root)); 
ListJoin<T, Entity> join = root.join(T_.someList); 
Predicate predicate = builder.ge(join.get(Entity_.id), "myId"); 
cqCount.where(predicate); 
TypedQuery<Long> q = em.createQuery(cqCount); 

En cuanto a su pseudo-código, parece que está utilizando la clase equivocada en el método de unión: debe ser la clase de partida, no la clase de destino.

+0

Leer su respuesta simple después de una buena noche me acaba de sacar de 10 horas de lucha con esta combinación de consulta y criterio de cosas ...:) – Raindal

6

he hecho esto:

public Long getRowCount(CriteriaQuery criteriaQuery,CriteriaBuilder criteriaBuilder,Root<?> root){ 
    CriteriaQuery<Long> countCriteria = criteriaBuilder.createQuery(Long.class); 
    Root<?> entityRoot = countCriteria.from(root.getJavaType()); 
    entityRoot.alias(root.getAlias()); 
    doJoins(root.getJoins(),entityRoot); 
    countCriteria.select(criteriaBuilder.count(entityRoot)); 
    countCriteria.where(criteriaQuery.getRestriction()); 
    return this.entityManager.createQuery(countCriteria).getSingleResult(); 
} 

private void doJoins(Set<? extends Join<?, ?>> joins,Root<?> root_){ 
    for(Join<?,?> join: joins){ 
     Join<?,?> joined = root_.join(join.getAttribute().getName(),join.getJoinType()); 
     doJoins(join.getJoins(), joined); 
    } 
} 

private void doJoins(Set<? extends Join<?, ?>> joins,Join<?,?> root_){ 
    for(Join<?,?> join: joins){ 
     Join<?,?> joined = root_.join(join.getAttribute().getName(),join.getJoinType()); 
     doJoins(join.getJoins(),joined); 
    } 
} 

por supuesto que no es necesario Root como parámetro de entrada que podría obtener de criterios de consulta,

+0

Buena respuesta, gracias por compartir tu trabajo. –

3

@ lubo08 dio respuesta correcta - felicitaciones para él . Pero por dos casos de esquina a su/su código no funcionará:

  • Cuando las restricciones de criterios de consulta para utilizar alias une - a continuación, COUNT requieren también estos alias que se establezcan.
  • Cuando el uso de criterios de consulta buscar a unirse [root.fetch(..) en lugar de root.join(..)]

Así que para la integridad me atreví a mejorar su/su solución y presente a continuación:

public <T> long count(final CriteriaBuilder cb, final CriteriaQuery<T> criteria, 
     Root<T> root) { 
    CriteriaQuery<Long> query = createCountQuery(cb, criteria, root); 
    return this.entityManager.createQuery(query).getSingleResult(); 
} 

private <T> CriteriaQuery<Long> createCountQuery(final CriteriaBuilder cb, 
     final CriteriaQuery<T> criteria, final Root<T> root) { 

    final CriteriaQuery<Long> countQuery = cb.createQuery(Long.class); 
    final Root<T> countRoot = countQuery.from(criteria.getResultType()); 

    doJoins(root.getJoins(), countRoot); 
    doJoinsOnFetches(root.getFetches(), countRoot); 

    countQuery.select(cb.count(countRoot)); 
    countQuery.where(criteria.getRestriction()); 

    countRoot.alias(root.getAlias()); 

    return countQuery.distinct(criteria.isDistinct()); 
} 

@SuppressWarnings("unchecked") 
private void doJoinsOnFetches(Set<? extends Fetch<?, ?>> joins, Root<?> root) { 
    doJoins((Set<? extends Join<?, ?>>) joins, root); 
} 

private void doJoins(Set<? extends Join<?, ?>> joins, Root<?> root) { 
    for (Join<?, ?> join : joins) { 
     Join<?, ?> joined = root.join(join.getAttribute().getName(), join.getJoinType()); 
     joined.alias(join.getAlias()); 
     doJoins(join.getJoins(), joined); 
    } 
} 

private void doJoins(Set<? extends Join<?, ?>> joins, Join<?, ?> root) { 
    for (Join<?, ?> join : joins) { 
     Join<?, ?> joined = root.join(join.getAttribute().getName(), join.getJoinType()); 
     joined.alias(join.getAlias()); 
     doJoins(join.getJoins(), joined); 
    } 
} 

Aunque todavía es no es perfecto, porque solo se respeta una raíz.
Pero espero que ayude a alguien.

Cuestiones relacionadas