2010-05-21 6 views
83

Ich bin ziemlich neu in JPA 2 und es ist CriteriaBuilder/CriteriaQuery API:In JPA 2, ein CriteriaQuery verwenden, wie die Ergebnisse zählen

CriteriaQuery javadoc

CriteriaQuery in the Java EE 6 tutorial

Ich möchte die Ergebnisse zählen einer CriteriaQuery, ohne sie tatsächlich abzurufen. Ist das möglich, ich habe keine solche Methode finden, ist der einzige Weg wäre, dies zu tun:

CriteriaBuilder cb = entityManager.getCriteriaBuilder(); 

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

// initialize predicates here 

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

Und das kann nicht der richtige Weg, es zu tun ...

Gibt es ein Lösung?

Antwort

158

Eine Abfrage des Typs MyEntity wird MyEntity zurückzukehren. Sie möchten eine Abfrage für eine .

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

Offensichtlich werden Sie Ihren Ausdruck mit den im Beispiel übersprungenen Einschränkungen und Gruppierungen etc. aufbauen wollen.

+3

Das ist, was ich mir gedacht habe, danke. Aber das bedeutet, dass ich nicht die gleiche Abfrage-Instanz für die Abfrage der Anzahl der Ergebnisse und der tatsächlichen Ergebnisse verwenden kann, von denen ich weiß, dass sie analog zu SQL sind, die aber diese API viel mehr OOP-ähnlich machen würde. Nun, zumindest kann ich einige der Prädikate wiederverwenden, denke ich. –

+4

@Barett Wenn es eine ziemlich große Anzahl ist, möchten Sie wahrscheinlich keine Liste von Hunderten oder Tausenden von Entitäten in den Speicher laden, nur um herauszufinden, wie viele es gibt! – Affe

+0

@Barett dies wird bei Seitenumbruch viel verwendet. Daher die Notwendigkeit für eine Gesamtzahl und nur eine Teilmenge der tatsächlichen Zeilen. – 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

Verwenden Sie countDistinct anstelle von count.

+0

Sie haben die Frage nicht vollständig erhalten. –

24

ich die cb.createQuery mit diesem heraus sortiert haben() (ohne den Ergebnistyp Parameter):

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

Hoffe, es hilft :)

1

Es ist ein bisschen schwierig ist, auf die je JPA 2 Implementierung Sie verwenden, diese funktioniert für Eclipse 2.4.1, aber nicht für Hibernate, hier eine allgemeine CriteriaQuery Zahl für 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

Was ist, wenn die Abfrage Join (s) hat? – Dave

+0

Ich denke, der einzige Fall, der gefährlich wäre, ist, wenn Sie eine linke Verbindung haben und die ausgewählte Wurzel nicht die Haupteinheit ist. Ansonsten ist es egal, weil die Zählung unabhängig von der ausgewählten Entität gleich ist. Wie bei linken Join-Entitäten bin ich mir ziemlich sicher, dass die erste Entität in der Auswahl die Referenz ist. Wenn zum Beispiel Studenten an Kursen teilnehmen, dann sollte die Auswahl des Schülers die natürliche Sache sein, da es Kurse geben kann, die der Student nicht ist eingeschrieben. –

+1

Wenn die ursprüngliche Abfrage eine groupBy-Abfrage ist, wäre das Ergebnis eine Zählung für jede Gruppe. Wenn wir eine CriteriaQuery in eine SubQuery umwandeln können, zählen Sie die Unterabfrage, sie würde in allen Fällen funktionieren. Können wir das tun? – Dave

6

Wie andere Antworten sind richtig, aber zu einfach, so zur Vollständigkeit präsentiere ich unten Codeausschnitt, um SELECT COUNT auf einer ausgefeilten JPA Kriterienabfrage durchzuführen (mit mehrfachen Verbindungen, Fetches, Bedingungen).

Es ist leicht modifiziert 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); 
    } 
} 

Ich hoffe, es spart jemand Zeit.

Da IMHO JPA-Kriterien-API ist nicht intuitiv noch gut lesbar.

+1

es ist irgendwie süß, dass Sie tatsächlich denken, dass Ihr Code "anspruchsvoll" ist, weil Sie ein paar Zeilen zu den Beispielen hinzugefügt ... während es immer noch fehleranfällig ist. – specializt

+1

@specializzt natürlich ist es nicht perfekt - z.B. Über der Lösung fehlt noch eine rekursive Joins bei Fetches. Aber denkst du, dass ich deswegen nicht meine Gedanken teilen sollte? IMHO Wissen teilen ist die Hauptidee hinter StackOverfow. –

+0

Rekursion auf Datenbanken ist immer die denkbar schlechteste Lösung ... das ist ein Anfängerfehler. – specializt

0

Sie können auch Projektionen verwenden:

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

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

Ich fürchte, Projektionen API ist Hibernate spezifisch, aber die Frage ist über JPA 2. – gersonZaragocin

Verwandte Themen