In JPA 2, utilizzando CriteriaQuery, come contare i risultati


114

Sono piuttosto nuovo in JPA 2 ed è API CriteriaBuilder / CriteriaQuery:

CriteriaQuery javadoc

CriteriaQuery nel tutorial Java EE 6

Vorrei contare i risultati di una CriteriaQuery senza recuperarli effettivamente. È possibile, non ho trovato alcun metodo del genere, l'unico modo sarebbe farlo:

CriteriaBuilder cb = entityManager.getCriteriaBuilder();

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

// initialize predicates here

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

E questo non può essere il modo corretto per farlo ...

C'è una soluzione?


Sarebbe molto utile se qualcuno potesse aiutare o includere nelle risposte di seguito. Come ottenere la seguente query di conteggio utilizzando l'API dei criteri JPA? seleziona count (distinto col1, col2, col3) da my_table;
Bhavesh

cercando la risposta qui sotto ma invece di qb.count usa qb.distinctCount @Bhavesh
Tonino

Risposte:


220

MyEntityRestituirà una query di tipo MyEntity. Vuoi una query per un file 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();

Ovviamente vorrai costruire la tua espressione con tutte le restrizioni, i raggruppamenti ecc. Che hai saltato nell'esempio.


3
Questo è quello che avevo pensato io stesso, grazie. Ma ciò significa che non posso utilizzare la stessa istanza di query per interrogare il numero di risultati e i risultati effettivi che so essere analoghi a SQL, ma che renderebbero questa API molto più simile a OOP. Beh, almeno posso riutilizzare alcuni dei predicati, immagino.
Sean Patrick Floyd

6
@Barett, se è un conteggio piuttosto grande, probabilmente non vuoi caricare un elenco di centinaia o migliaia di entità in memoria solo per scoprire quante sono!
Affe

@Barett questo è usato in caso di impaginazione molto. Da qui la necessità di un numero totale e solo di un sottoinsieme delle righe effettive.
gkephorus

2
Tenete presente che il qb.countviene fatto durante la Root<MyEntity>vostra query ( Root<MyEntity>myEntity = cq.from (MyEntity.class)) e questo è spesso già nel vostro normale codice di selezione e quando vi dimenticate vi ritroverete con un join to self.
gkephorus

2
Per riutilizzare gli stessi criteri per il recupero degli oggetti e il conteggio potrebbe essere necessario utilizzare alias nella root, vedere forum.hibernate.org/viewtopic.php?p=2471522#p2471522 per un esempio.
Piscina

31

Ho risolto questo problema usando cb.createQuery () (senza il parametro del tipo di risultato):

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

Spero che sia d'aiuto :)


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

Poiché altre risposte sono corrette, ma troppo semplici, quindi per completezza vi presento di seguito frammento di codice da eseguire SELECT COUNTsu un sofisticato query dei criteri JPA (con più join, recuperi, condizioni).

È leggermente modificata questa risposta .

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

Spero che faccia risparmiare tempo a qualcuno.

Perché IMHO JPA Criteria API non è intuitiva né abbastanza leggibile.


2
@specializt ovviamente non è perfetto - ad esempio, la soluzione sopra manca ancora un join ricorsivo sui recuperi. Ma pensi che solo per questo non dovrei condividere i miei pensieri? La condivisione della conoscenza IMHO è l'idea principale alla base di StackOverfow.
G. Demecki

La ricorsione sui database è sempre la peggiore soluzione possibile immaginabile ... questo è un errore dei principianti.
specializt

@specializt recursion on databases? Stavo parlando della ricorsione a livello di API. Non confondere questi concetti :-) JPA viene fornito con un'API molto potente / complessa che ti consente di eseguire più join / fetch / aggregazioni / alias ecc. In una singola query. Devi affrontarlo mentre conti.
G. Demecki

1
Apparentemente non hai ancora capito come funziona JPA: la stragrande maggioranza di voi criteri verrà mappata su query di database appropriate, inclusi questi join (estremamente strani). Attiva l'output SQL e osserva il tuo errore: non esiste un "livello API", JPA è un livello ASTRAZIONE
specializzato

molto probabilmente, vedrai molti JOIN a cascata, perché JPA non può ancora creare automaticamente funzioni SQL; ma questo cambierà prima o poi ... probabilmente con JPA 3, ricordo le discussioni su queste cose
specializt

5

È un po 'complicato, a seconda dell'implementazione JPA 2 che usi, questa funziona per EclipseLink 2.4.1, ma non per Hibernate, qui un conteggio CriteriaQuery generico per 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'altro giorno sono migrato da EclipseLink a Hibernate e ho dovuto cambiare la mia funzione di conteggio come segue, quindi sentiti libero di usarli perché questo è un problema difficile da risolvere, potrebbe non funzionare per il tuo caso, è stato utilizzato da Hibernate 4.x, nota che non cerco di indovinare quale sia la radice, invece lo passo dalla query in modo che il problema sia risolto, troppi casi d'angolo ambigui per provare a indovinare:

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

cosa succede se la query ha join?
Dave

Penso che l'unico caso che sarebbe pericoloso è quando hai un join sinistro e la radice selezionata non è l'entità principale. Altrimenti non importa, perché il conteggio sarà lo stesso indipendentemente dall'entità selezionata. Per quanto riguarda le entità left join, sono abbastanza sicuro che la prima entità nella selezione sia quella di riferimento, ad esempio, se hai lasciato degli studenti che si iscrivono ai corsi, scegliere uno studente dovrebbe essere la cosa naturale perché potrebbero esserci corsi che lo studente non è iscritto.
Guido Medina

1
Se la query originale è groupBy query, il risultato sarebbe un conteggio per ogni gruppo. Se possiamo trasformare una CriteriaQuery in una SubQuery, quindi contare la sottoquery, funzionerebbe in tutti i casi. Possiamo farlo?
Dave

Ciao @ Dave, sono giunto alla tua stessa conclusione, la vera soluzione sarebbe essere in grado di trasformare le query in sottoquery, che funzionerebbero per tutti i casi, anche per il conteggio delle righe dopo un groupBy. In realtà non riesco a trovare una ragione sul motivo per cui le diverse classi di CriteriaQuery e Subquery, o almeno il fatto che l'interfaccia comune che condividono, AbstractQuery, non definisce un metodo di selezione. Per questo motivo, non c'è modo di riutilizzare quasi tutto. Hai trovato una soluzione pulita per riutilizzare una query raggruppata per conteggio delle righe?
Amanda Tarafa Mas

1

Puoi anche usare le proiezioni:

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

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

1
Temo che l'API Projections sia specifica per Hibernate, ma la domanda riguarda JPA 2.
gersonZaragocin

Tuttavia, la trovo un'aggiunta utile, ma forse avrebbe dovuto essere un commento. Puoi espandere la tua risposta per includere la risposta completa specifica di Hibernate?
Benny Bottema

gersonZaragocin è d'accordo, ma non ci sono blocchi di codice nei commenti
Pavel Evstigneev

0

Con Spring Data Jpa, possiamo utilizzare questo metodo:

    /*
     * (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()));
    }
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.