2010-10-22 7 views
11

Sto cercando di implementare il seguente metodo comodo:JPA 2.0: conta per CriteriaQuery arbitraria?

/** 
* Counts the number of results of a search. 
* @param criteria The criteria for the query. 
* @return The number of results of the query. 
*/ 
public int findCountByCriteria(CriteriaQuery<?> criteria); 

In Hibernate, questo è fatto da

criteria.setProjection(Projections.rowCount()); 

Qual è l'equivalente a quanto sopra in JPA? Ho trovato numerosi esempi di conteggi semplici, ma nessuno di loro ha fatto uso di una CriteriaQuery il cui conteggio delle righe deve essere determinato.

EDIT:

ho purtroppo scoperto che @ risposta di Pascal non è quella corretta. Il problema è molto sottile e mostra solo quando si utilizza unisce:

// Same query, but readable: 
// SELECT * 
// FROM Brain b 
// WHERE b.iq = 170 

CriteriaQuery<Person> query = cb.createQuery(Person.class); 
Root<Person> root = query.from(Person.class); 
Join<Object, Object> brainJoin = root.join("brain"); 
Predicate iqPredicate = cb.equal(brainJoin.<Integer>get("iq"), 170); 
query.select(root).where(iqPredicate); 

Quando si chiama findCountByCriteria(query), muore con la seguente eccezione:

org.hibernate.hql.ast.QuerySyntaxException: Invalid path: 'generatedAlias1.iq' [select count(generatedAlias0) from xxx.tests.person.dom.Person as generatedAlias0 where generatedAlias1.iq=170] 

C'è un altro modo per fornire un tale metodo CountByCriteria ?

+2

Dove è possibile trovare una soluzione? Sono allo stesso incrocio e non sono sicuro di come procedere. Grazie. – Ittai

+0

@Ittai: Sono abbastanza sicuro che non sia possibile dopo tutto. – blubb

+0

@Ittai: Apparentemente, è possibile dopotutto. Per favore, vedi la risposta di Jose Luis Martin. Non l'ho provato, ma mi sembra la vera soluzione. – blubb

risposta

17

ho scritto una classe di utilità, JDAL JpaUtils di farlo:

  • risultati di conteggio: Long count = JpaUtils.count(em, criteriaQuery);
  • CriteriaQueries copia: JpaUtils.copyCriteria(em, criteriaQueryFrom, criteriaQueryTo);
  • ottenere i criteri di conteggio: CriteriaQuery<Long> countCriteria = JpaUtils.countCriteria(em, criteria)

e così via ...

Se siete interessati nel codice sorgente, vedere JpaUtils.java

+0

Dovresti menzionare che sei l'autore di detta classe di utilità. Oltre a questo, questa risposta merita il massimo dei voti e assegnerò la generosità a questa risposta non appena sarà scaduto il periodo di attesa obbligatorio di 24 ore. Grazie! – blubb

+0

@blubb Grazie, ho appena modificato. Non completamente testato ma sembra funzionare con criteri complessi –

+0

Questo toolkit si basa su Spring, che il richiedente non ha mai detto di avere ... – Amalgovinus

1

Stai cercando qualcosa di simile?

/** 
* Counts the number of results of a search. 
* 
* @param criteria The criteria for the query. 
* @return The number of results of the query. 
*/ 
public <T> Long findCountByCriteria(CriteriaQuery<?> criteria) { 
    CriteriaBuilder builder = em.getCriteriaBuilder(); 

    CriteriaQuery<Long> countCriteria = builder.createQuery(Long.class); 
    Root<?> entityRoot = countCriteria.from(criteria.getResultType()); 
    countCriteria.select(builder.count(entityRoot)); 
    countCriteria.where(criteria.getRestriction()); 

    return em.createQuery(countCriteria).getSingleResult(); 
} 

che si potrebbe usare in questo modo:

// a search based on the Criteria API 
CriteriaBuilder builder = em.getCriteriaBuilder(); 
CriteriaQuery<Person> criteria = builder.createQuery(Person.class); 
Root<Person> personRoot = criteria.from(Person.class); 
criteria.select(personRoot); 
Predicate personRestriction = builder.and(
    builder.equal(personRoot.get(Person_.gender), Gender.MALE), 
    builder.equal(personRoot.get(Person_.relationshipStatus), RelationshipStatus.SINGLE) 
); 
criteria.where(personRestriction); 
//... 

// and to get the result count of the above query 
Long count = findCountByCriteria(criteria); 

PS: Non so se questo è il/modo migliore giusto per attuare tale, ancora imparando l'API Criteri ...

+0

@Simon: Sono contento che ci abbia aiutato. Sentiti libero di accettare la risposta (il segno di spunta verde sotto il contatore dei voti a sinistra). –

+0

Sfortunatamente, questa soluzione funziona solo per query banali. (Vedi la modifica sulla domanda per una spiegazione, è troppo lungo per un commento.) – blubb

-3

mi faccio somethig come thath con Hibernate e criteria api

public Long getRowsCount(List<Criterion> restrictions) { 
     Criteria criteria = getSession().createCriteria(ThePersistenclass.class); 
     for (Criterion x : restrictions) 
      criteria.add(x); 
    return criteria.setProjection(Projections.rowCount()).uniqueResult();   

}

speranza aiuto

+0

So che questo è possibile con l'API Hibernate (come ho affermato nella domanda). Mi interessa solo un'alternativa usando l'interfaccia JPA. – blubb

3

ho risolto questa situazione con il CB.createQuery() (senza il parametro 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(); 
    } 
} 

Speranza che aiuta :)

Quello che ho fatto è qualcosa di simile a un costruttore di un CriteriaBuilder cui è possibile costruire una query e lista chiamate() o count() con gli stessi criteri restrizioni

1

L'intera idea delle query sui criteri è che sono fortemente tipizzate. Pertanto, ciascuna soluzione, in cui si utilizzano tipi non elaborati (senza generici in CriteriaQuery o Root o Root), queste soluzioni sono contrarie a tale idea principale. Ho appena incontrato lo stesso problema e sto lottando per risolverlo in un modo "corretto" (insieme a JPA2).

1

Nessuna delle soluzioni di cui sopra funziona per EclipseLink 2.4.1, sono finite tutte con un conteggio su un prodotto cartesiano (N^2), ecco un piccolo hack per EclipseLink, l'unico inconveniente è che non lo so cosa succederà se si seleziona DA più di un'entità, proverà a contare dalla prima radice trovata della CriteriaQuery, questa soluzione NON funziona per Hibernate (JDAL funziona, ma JDAL non funziona 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(); 
    } 
+0

Oops !, EclipseLink aggiunge le radici dal predicato ai criteri. Io lavoro solo per la versione 2.0. Grazie! –

1

se si desidera il risultato e il conteggio di tutti gli elementi come Page di Spring Data -Element è possibile eseguire due query. Quello che puoi fare è separare i criteri dall'esecuzione della query.

Esempio per trovare gli utenti da Città

public List<User> getUsers(int userid, String city, other values ...) { 

    CriteriaBuilder cb = em.getCriteriaBuilder(); 
    CriteriaQuery<User> q = cb.createQuery(User.class); 
    Root<User> c = q.from(User.class); 

    List<Predicate> conditions = createConditions(c, cb, userid, city, ...other values); 
    List<User> users = em.createQuery(q.select(c).where(conditions.toArray(new Predicate[] {})).distinct(true)) 
      .setMaxResults(PAGE_ELEMENTS).setFirstResult(page * PAGE_ELEMENTS).getResultList(); 
    return users; 
} 

addiional Metodo getUser si può costruire un secondo che conterà gli elementi

public Long getElemCount(int userid, String city, ...other values) { 

    CriteriaBuilder cb = em.getCriteriaBuilder(); 
    CriteriaQuery<Long> q = cb.createQuery(Long.class); 
    Root<Location> root = q.from(Location.class); 

    List<Predicate> conditions = createConditions(root, cb, userid, page, city, filter, module, isActive); 
    Long userCount = em.createQuery(q.select(cb.count(root)).where(conditions.toArray(new Predicate[] {})).distinct(true)) 
      .getSingleResult(); 

    return userCount; 
} 

e il metodo createConditions gestirà entrambi, in modo da fare non è necessario duplicare la logica per i criteri.

<T> List<Predicate> createConditions(Root<T> root, CriteriaBuilder cb, int userid, String city, ... other values) { 

    Join<User, SecondEntity> usr = root.join("someField"); 
    // add joins as you wish 

    /* 
    * Build Conditions 
    */ 
    List<Predicate> conditions = new ArrayList<>(); 

    conditions.add(cb.equal(root.get("id"), userid)); 

    if (!city.equals("")) { 
     conditions.add(cb.like(...)); 
    } 

    // some more conditions... 

    return conditions; 
} 

nel vostro contoller si può fare qualcosa di simile

lungo elementCount = yourCriteriaClassInstance.getElementCount (...); Elenco utenti = yourCriteriaClassInstance.getUsers (...)

Problemi correlati