In JPA 2 mithilfe einer CriteriaQuery, wie Ergebnisse gezählt werden


114

Ich bin ziemlich neu in JPA 2 und es ist CriteriaBuilder / CriteriaQuery API:

CriteriaQuery Javadoc

CriteriaQuery im Java EE 6 Tutorial

Ich möchte die Ergebnisse einer CriteriaQuery zählen, ohne sie tatsächlich abzurufen. Ist das möglich, habe ich keine solche Methode gefunden, 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 sein ...

Gibt es eine Lösung?


Es wäre sehr nützlich, wenn jemand helfen oder in die Antworten unten aufnehmen kann. Wie erreicht man die folgende Zählabfrage mithilfe der JPA-Kriterien-API? Wählen Sie count (different col1, col2, col3) aus my_table aus.
Bhavesh

Schauen Sie sich die Antwort unten an, aber verwenden Sie anstelle von qb.count qb.distinctCount @Bhavesh
Tonino

Antworten:


220

Eine Abfrage vom Typ MyEntitywird zurückgegeben MyEntity. Sie möchten eine Abfrage für a 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();

Natürlich möchten Sie Ihren Ausdruck mit den Einschränkungen und Gruppierungen usw. aufbauen, die Sie im Beispiel übersprungen haben.


3
Das hatte ich mir gedacht, danke. Das bedeutet jedoch, dass ich nicht dieselbe Abfrageinstanz verwenden kann, um nach der Anzahl der Ergebnisse und den tatsächlichen Ergebnissen zu fragen, von denen ich weiß, dass sie mit SQL vergleichbar sind, was diese API jedoch viel OOP-ähnlicher machen würde. Nun, zumindest kann ich einige der Prädikate wiederverwenden, denke ich.
Sean Patrick Floyd

6
@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

@Barett Dies wird häufig bei Paginierung verwendet. Daher ist eine Gesamtzahl und nur eine Teilmenge der tatsächlichen Zeilen erforderlich.
Gkephorus

2
Beachten Sie jedoch, dass qb.countdies über Root<MyEntity>Ihre Abfrage ( Root<MyEntity>myEntity = cq.from (MyEntity.class)) erfolgt und dies häufig bereits in Ihrem normalen Auswahlcode enthalten ist und wenn Sie vergessen, dass Sie sich selbst verbinden.
Gkephorus

2
Ein Beispiel finden Sie unter forum.hibernate.org/viewtopic.php?p=2471522#p2471522, um dieselben Kriterien für das Abrufen von Objekten und die Anzahl der möglicherweise wiederverwendbaren Aliase im Stammverzeichnis wiederzuverwenden .
Pool

31

Ich habe dies mit cb.createQuery () (ohne den Parameter für den Ergebnistyp) geklärt:

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

Ich hoffe es hilft :)


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

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

12

Da andere Antworten korrekt, aber zu einfach sind, stelle ich der Vollständigkeit halber das folgende Code-Snippet vor, um SELECT COUNTeine anspruchsvolle JPA-Kriterien-Abfrage (mit mehreren Verknüpfungen, Abrufen, Bedingungen) durchzuführen .

Diese Antwort wurde leicht geändert .

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

Hoffe, es spart jemandem Zeit.

Weil die IMHO JPA Criteria API weder intuitiv noch gut lesbar ist.


2
@specializt ist natürlich nicht perfekt - zB fehlt bei der obigen Lösung immer noch eine rekursive Verknüpfung auf Abrufen. Aber denkst du, dass ich nur deswegen meine Gedanken nicht teilen sollte? IMHO Wissen zu teilen ist die Hauptidee hinter StackOverfow.
G. Demecki

Rekursion in Datenbanken ist immer die schlechteste Lösung, die man sich vorstellen kann ... das ist ein Anfängerfehler.
Spezialisiert

@specializt recursion on databases? Ich habe über Rekursion auf API-Ebene gesprochen. Verwechseln Sie diese Konzepte nicht :-) JPA wird mit einer sehr leistungsstarken / komplexen API geliefert, mit der Sie mehrere Verknüpfungen / Abrufe / Aggregationen / Aliase usw. in einer einzigen Abfrage ausführen können. Sie müssen sich beim Zählen damit auseinandersetzen.
G. Demecki

1
Anscheinend haben Sie noch nicht verstanden, wie JPA funktioniert - die überwiegende Mehrheit Ihrer Kriterien wird auf entsprechende Datenbankabfragen abgebildet, einschließlich dieser (äußerst seltsamen) Verknüpfungen. Aktivieren Sie die SQL-Ausgabe und beobachten Sie Ihren Fehler - es gibt keine "API-Schicht", JPA ist eine ABSTRACTION-Schicht
spezialisiert auf den

Höchstwahrscheinlich werden Sie viele kaskadierte JOINs sehen - da JPA SQL-Funktionen noch nicht automatisch erstellen kann; aber das wird sich irgendwann ändern ... wahrscheinlich mit JPA 3, ich erinnere mich an Diskussionen über diese Dinge
spezialisiert auf den

5

Abhängig von der von Ihnen verwendeten JPA 2-Implementierung ist dies etwas schwierig. Diese funktioniert für EclipseLink 2.4.1, jedoch nicht für den Ruhezustand, hier eine generische CriteriaQuery-Anzahl 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();
  }

Neulich bin ich von EclipseLink in den Ruhezustand migriert und musste meine Zählfunktion auf die folgende ändern. Sie können sie also auch verwenden, da dies ein schwer zu lösendes Problem ist. Es funktioniert möglicherweise nicht für Ihren Fall, da es seit dem Ruhezustand verwendet wird 4.x, beachte, dass ich nicht versuche zu erraten, welches die Wurzel ist, sondern es von der Abfrage übergebe, damit das Problem gelöst ist, zu viele mehrdeutige Eckfälle, um zu versuchen zu erraten:

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

Was ist, wenn die Abfrage Verknüpfungen hat?
Dave

Ich denke, der einzige Fall, der gefährlich wäre, ist, wenn Sie einen linken Join haben und die ausgewählte Wurzel nicht die Hauptentität ist. Andernfalls spielt es keine Rolle, da die Anzahl unabhängig von der ausgewählten Entität gleich ist. Was Entitäten für Linksverknüpfungen betrifft, bin ich mir ziemlich sicher, dass die erste Entität in der Auswahl die Referenzentität ist. Wenn Sie beispielsweise Schüler verlassen haben, die Beitrittskurse verlassen, sollte die Auswahl von Schülern die natürliche Sache sein, da es Kurse geben kann, die der Schüler nicht hat eingeschrieben.
Guido Medina

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

Hallo @ Dave, ich bin zu dem gleichen Schluss gekommen wie Sie. Die wirkliche Lösung wäre, Abfragen in Unterabfragen umzuwandeln, die in allen Fällen funktionieren würden, selbst zum Zählen von Zeilen nach einer groupBy. Eigentlich kann ich keinen Grund finden, warum die verschiedenen Klassen für CriteriaQuery und Subquery oder zumindest die Tatsache, dass die gemeinsame Schnittstelle, die sie gemeinsam nutzen, AbstractQuery, keine Auswahlmethode definiert. Aus diesem Grund gibt es keine Möglichkeit, fast alles wiederzuverwenden. Haben Sie eine saubere Lösung gefunden, um eine von Abfragen unterstützte Abfrage zum Zählen der Zeilen wiederzuverwenden?
Amanda Tarafa Mas

1

Sie können auch Projektionen verwenden:

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

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

1
Ich fürchte, die Projektions-API ist spezifisch für den
Ruhezustand,

Trotzdem finde ich es eine nützliche Ergänzung, aber vielleicht hätte es ein Kommentar sein sollen. Können Sie Ihre Antwort um die vollständige Antwort für den Ruhezustand erweitern?
Benny Bottema

gersonZaragocin stimmt zu, aber es gibt keine Codeblöcke in Kommentaren
Pavel Evstigneev

0

Mit Spring Data Jpa können wir diese Methode verwenden:

    /*
     * (non-Javadoc)
     * @see org.springframework.data.jpa.repository.JpaSpecificationExecutor#count(org.springframework.data.jpa.domain.Specification)
     */
    @Override
    public long count(@Nullable Specification<T> spec) {
        return executeCountQuery(getCountQuery(spec, getDomainClass()));
    }
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.