2010-05-21 11 views
83

Je suis assez nouveau pour JPA 2 et API CriteriaBuilder/de CriteriaQuery:En JPA 2, en utilisant un CriteriaQuery, comment compter les résultats

CriteriaQuery javadoc

CriteriaQuery in the Java EE 6 tutorial

je voudrais compter les résultats d'une CriteriaQuery sans les récupérer. Est-ce possible, je ne trouve pas une telle méthode, la seule façon serait de le faire:

CriteriaBuilder cb = entityManager.getCriteriaBuilder(); 

CriteriaQuery<MyEntity> cq = cb 
     .createQuery(MyEntityclass); 

// initialize predicates here 

return entityManager.createQuery(cq).getResultList().size(); 

Et ce ne peut pas être la bonne façon de le faire ...

Y at-il un Solution?

Répondre

158

Une requête de type MyEntity va revenir MyEntity. Vous voulez une requête pour un Long.

CriteriaBuilder qb = entityManager.getCriteriaBuilder(); 
CriteriaQuery<Long> cq = qb.createQuery(Long.class); 
cq.select(qb.count(cq.from(MyEntity.class))); 
cq.where(/*your stuff*/); 
return entityManager.createQuery(cq).getSingleResult(); 

De toute évidence, vous voudrez construire votre expression avec les restrictions et les groupements etc. que vous avez omis dans l'exemple.

+3

C'est ce que je me suis figuré, merci. Mais cela signifie que je ne peux pas utiliser la même instance de requête pour rechercher le nombre de résultats et les résultats réels qui, je le sais, sont analogues à SQL, mais qui rendraient cette API beaucoup plus semblable à la POO. Eh bien, au moins je peux réutiliser certains des prédicats, je suppose. –

+4

@Barett Si c'est un nombre plutôt important, vous ne voulez probablement pas charger une liste de centaines ou de milliers d'entités en mémoire juste pour savoir combien il y en a! – Affe

+0

@Barett ceci est beaucoup utilisé en cas de pagination. D'où la nécessité d'un nombre total et seulement un sous-ensemble des lignes réelles. – gkephorus

18
CriteriaBuilder cb = em.getCriteriaBuilder(); 
CriteriaQuery<Long> cq = cb.createQuery(Long.class); 
cq.select(cb.count(cq.from(MyEntity.class))); 

return em.createQuery(cq).getSingleResult(); 
-5

Utilisez countDistinct au lieu de count.

+0

Vous n'avez pas complètement compris la question. –

24

J'ai triés ceci en utilisant la cb.createQuery() (sans le paramètre de type de résultat):

public class Blah() { 

    CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder(); 
    CriteriaQuery query = criteriaBuilder.createQuery(); 
    Root<Entity> root; 
    Predicate whereClause; 
    EntityManager entityManager; 
    Class<Entity> domainClass; 

    ... Methods to create where clause ... 

    public Blah(EntityManager entityManager, Class<Entity> domainClass) { 
     this.entityManager = entityManager; 
     this.domainClass = domainClass; 
     criteriaBuilder = entityManager.getCriteriaBuilder(); 
     query = criteriaBuilder.createQuery(); 
     whereClause = criteriaBuilder.equal(criteriaBuilder.literal(1), 1); 
     root = query.from(domainClass); 
    } 

    public CriteriaQuery<Entity> getQuery() { 
     query.select(root); 
     query.where(whereClause); 
     return query; 
    } 

    public CriteriaQuery<Long> getQueryForCount() { 
     query.select(criteriaBuilder.count(root)); 
     query.where(whereClause); 
     return query; 
    } 

    public List<Entity> list() { 
     TypedQuery<Entity> q = this.entityManager.createQuery(this.getQuery()); 
     return q.getResultList(); 
    } 

    public Long count() { 
     TypedQuery<Long> q = this.entityManager.createQuery(this.getQueryForCount()); 
     return q.getSingleResult(); 
    } 
} 

Hope it helps :)

1

Il est un peu difficile, selon la JPA 2 mise en œuvre que vous utilisez, celui-ci travaille pour EclipseLink 2.4.1, mais ne pas Hibernate, ici un nombre de CriteriaQuery générique pour EclipseLink:

public static Long count(final EntityManager em, final CriteriaQuery<?> criteria) 
    { 
    final CriteriaBuilder builder=em.getCriteriaBuilder(); 
    final CriteriaQuery<Long> countCriteria=builder.createQuery(Long.class); 
    countCriteria.select(builder.count(criteria.getRoots().iterator().next())); 
    final Predicate 
      groupRestriction=criteria.getGroupRestriction(), 
      fromRestriction=criteria.getRestriction(); 
    if(groupRestriction != null){ 
     countCriteria.having(groupRestriction); 
    } 
    if(fromRestriction != null){ 
     countCriteria.where(fromRestriction); 
    } 
    countCriteria.groupBy(criteria.getGroupList()); 
    countCriteria.distinct(criteria.isDistinct()); 
    return em.createQuery(countCriteria).getSingleResult(); 
    } 
+0

Que faire si la requête a des jointures? – Dave

+0

Je pense que le seul cas qui serait dangereux est quand vous avez une jointure à gauche et que la racine choisie n'est pas l'entité principale. Sinon, cela n'a pas d'importance, car le nombre sera le même quelle que soit l'entité choisie. En ce qui concerne les entités de jointure gauche, je suis sûr que la première entité de la sélection est la référence, par exemple, si vous avez des étudiants qui quittent les cours, choisir l'étudiant devrait être naturel car il peut y avoir des cours inscrit. –

+1

Si la requête d'origine est une requête groupBy, le résultat serait un compte pour chaque groupe. Si nous pouvons créer une requête CriteriaQuery dans une sous-requête, puis compter la sous-requête, cela fonctionnera dans tous les cas. pouvons-nous faire cela? – Dave

6

réponses Comme les autres sont corrects, mais trop simple, donc pour l'exhaustivité je présente ci-dessous l'extrait de code pour effectuer SELECT COUNT sur un sophistiqué Requête de critères JPA (avec plusieurs jointures, extractions, conditions).

Il est légèrement modifié this answer.

public <T> long count(final CriteriaBuilder cb, final CriteriaQuery<T> selectQuery, 
     Root<T> root) { 
    CriteriaQuery<Long> query = createCountQuery(cb, selectQuery, 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); 
    } 
} 

Espérons que cela fasse gagner du temps à quelqu'un. Parce que l'API IMHO JPA Criteria n'est pas intuitive ni assez lisible.

+1

c'est un peu mignon que vous pensez réellement que votre code est "sophistiqué" parce que vous avez ajouté quelques lignes aux exemples ... alors qu'il est encore sujette aux erreurs. – specializt

+1

@specializt bien sûr ce n'est pas parfait - par ex. solution ci-dessus manque toujours une jointure récursive sur les récupérations. Mais pensez-vous que c'est juste pour cela que je ne devrais pas partager mes pensées? Le partage de connaissances à mon humble avis est l'idée principale de StackOverfow. –

+0

La récursion sur les bases de données est toujours la pire solution imaginable ... c'est une erreur de débutant. – specializt

0

Vous pouvez également utiliser les projections:

ProjectionList projection = Projections.projectionList(); 
projection.add(Projections.rowCount()); 
criteria.setProjection(projection); 

Long totalRows = (Long) criteria.list().get(0); 
+0

Je crains que l'API Projections est spécifique à Hibernate mais la question est posée à propos de JPA 2. – gersonZaragocin