147 votes

Dans JPA 2, en utilisant une CriteriaQuery, comment compter les résultats ?

Je suis plutôt novice en matière de JPA 2 et de son API CriteriaBuilder / CriteriaQuery :

CriteriaQuery javadoc

CriteriaQuery dans le tutoriel Java EE 6

Je voudrais compter les résultats d'une CriteriaQuery sans les récupérer réellement. Est-ce possible, je n'ai pas trouvé de méthode de ce type, la seule façon serait de faire ceci :

CriteriaBuilder cb = entityManager.getCriteriaBuilder();

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

// initialize predicates here

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

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

Y a-t-il une solution ?

0 votes

Il serait très utile si quelqu'un peut aider ou inclure dans les réponses ci-dessous. Comment réaliser la requête de comptage suivante en utilisant l'API de critères JPA ? select count(distinct col1, col2, col3) from my_table ;

0 votes

Regarder la réponse ci-dessous mais à la place de qb.count utiliser qb.distinctCount @Bhhavesh

272voto

Affe Points 24993

Une requête de type MyEntity va retourner 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();

Il est évident que vous voudrez construire votre expression avec les restrictions, les regroupements, etc. que vous avez omis dans l'exemple.

3 votes

C'est ce que j'avais compris moi-même, merci. Mais cela signifie que je ne peux pas utiliser la même instance de requête pour demander le nombre de résultats et les résultats réels, ce qui, je le sais, est analogue au SQL, mais qui rendrait cette API beaucoup plus proche de la POO. Au moins, je peux réutiliser certains des prédicats, je suppose.

6 votes

@Barett s'il s'agit d'un nombre assez 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 !

0 votes

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

36voto

reyiyo Points 111

J'ai résolu le problème en utilisant la méthode 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();
    }
}

J'espère que cela vous aidera :)

27voto

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

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

5voto

Guido Medina Points 56

C'est un peu délicat, cela dépend de l'implémentation de JPA 2 que vous utilisez, celle-ci fonctionne pour EclipseLink 2.4.1, mais pas pour Hibernate, voici un compte générique de CriteriaQuery 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();
  }

L'autre jour, j'ai migré d'EclipseLink vers Hibernate et j'ai dû changer ma fonction de comptage pour la suivante, alors n'hésitez pas à utiliser l'une ou l'autre car c'est un problème difficile à résoudre, cela peut ne pas fonctionner pour votre cas, c'est utilisé depuis Hibernate 4.x, remarquez que je n'essaie pas de deviner quelle est la racine, au lieu de cela je la passe depuis la requête, donc problème résolu, trop de cas de coin ambigus pour essayer de deviner :

  public static <T> long count(EntityManager em,Root<T> root,CriteriaQuery<T> criteria)
  {
    final CriteriaBuilder builder=em.getCriteriaBuilder();
    final CriteriaQuery<Long> countCriteria=builder.createQuery(Long.class);

    countCriteria.select(builder.count(root));

    for(Root<?> fromRoot : criteria.getRoots()){
      countCriteria.getRoots().add(fromRoot);
    }

    final Predicate whereRestriction=criteria.getRestriction();
    if(whereRestriction!=null){
      countCriteria.where(whereRestriction);
    }

    final Predicate groupRestriction=criteria.getGroupRestriction();
    if(groupRestriction!=null){
      countCriteria.having(groupRestriction);
    }

    countCriteria.groupBy(criteria.getGroupList());
    countCriteria.distinct(criteria.isDistinct());
    return em.createQuery(countCriteria).getSingleResult();
  }

0 votes

Que se passe-t-il si la requête comporte une ou plusieurs jointures ?

0 votes

Je pense que le seul cas qui pourrait être dangereux est celui où vous avez une jointure gauche et où la racine choisie n'est pas l'entité principale. Sinon, cela n'a pas d'importance, car le compte sera le même quelle que soit l'entité sélectionnée. En ce qui concerne les entités de jointure à gauche, je suis sûr que la première entité de la sélection est l'entité de référence. Par exemple, si vous avez des cours de jointure à gauche pour les étudiants, le choix de l'étudiant devrait être naturel car il pourrait y avoir des cours auxquels l'étudiant n'est pas inscrit.

1 votes

Si la requête originale est une requête groupBy, le résultat serait un compte pour chaque groupe. Si nous pouvons transformer une CriteriaQuery en SubQuery, puis compter la subquery, cela fonctionnerait dans tous les cas. Peut-on le faire ?

-6voto

Krzysztof Points 1

Utilisez countDistinct au lieu de count .

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X