2012-01-29 16 views
5

Sono nuovo in JPA e voglio implementare un DAO JPA generico e ho bisogno di trovare il numero di righe di un set di risultati di una query per implementare l'impaginazione. Dopo aver cercato sul Web, non riesco a trovare un modo pratico per farlo. Ecco il codice suggerito in molti articoli:Come contare il numero di righe di un CriteriaQuery JPA 2 in un DAO JPA generico?

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

Tuttavia, questo codice non funziona quando si utilizza join. Esiste un modo per contare le righe di un set di risultati della query utilizzando l'API dei criteri JPA?

UPDATE: qui è il codice che creano CriteriaQuery:

CriteriaQuery<T> queryDefinition = criteriaBuilder.createQuery(this.entityClass); 
    Root<T> root = queryDefinition.from(this.entityClass); 

e alcuni join può essere aggiunto alla radice finché la query è stata eseguita:

public Predicate addPredicate(Root<T> root) { 
       Predicate predicate = getEntityManager().getCriteriaBuilder().ge(root.join(Entity_.someList).get("id"), 13); 
       return predicate; 
} 

e l'eccezione generata è ad esempio:

org.hibernate.hql.ast.QuerySyntaxException: percorso non valido: 'generatedAlias1.id' [select count (generatedAlias0) da entity.Entity come generatedAlias0 dove (generatedAlias0.id> = 13L) e ( (generatedAlias1.id < = 34L))]

che generatedAlias1 dovrebbe essere su Entity e generateAlias0 dovrebbe essere sull'associazione a cui mi sono iscritto. Nota che implemento Join in modo corretto perché quando eseguo query senza conteggio, la query viene eseguita senza errori e Join funziona correttamente ma quando provo a eseguire query di conteggio genera un'eccezione.

+0

Puoi mostrarci come hai definito CriteriaQuery criteri – perissf

+0

@perissf Ho aggiornato il post. – stacker

+0

Dai un'occhiata ad altre risposte a problemi simili: http://stackoverflow.com/questions/9001289/jpa-hibernate-criteriabuilder-how-to-create-query-using-relationship-object/9002225#9002225 e http: // stackoverflow.com/questions/9025196/how-to-use-jpa-criteria-api-when-joining-many-tables/9025656#9025656 – perissf

risposta

1

Non riesco a dirti dove si trova il tuo problema, ma posso dirti che le query di conteggio con join funzionano bene, almeno in eclipselink jpa. La mia ipotesi è che questa è roba standard, quindi dovrebbe funzionare anche in ibernazione. Vorrei iniziare semplificando il codice per individuare il problema. Vedo che hai copiato alcuni pezzi della tua query cont dalla tua query principale. Forse puoi provare a cambiare un po 'questo approccio, solo per scopi di debug.

Quello che faccio di solito è:

CriteriaQuery cqCount = builder.createQuery(); 
Root<T> root = cq.from(T.class); 
cqCount.select(builder.count(root)); 
ListJoin<T, Entity> join = root.join(T_.someList); 
Predicate predicate = builder.ge(join.get(Entity_.id), "myId"); 
cqCount.where(predicate); 
TypedQuery<Long> q = em.createQuery(cqCount); 

Guardando il tuo pseudo-codice, sembra che si stia utilizzando la classe sbagliata nel metodo join: deve essere la classe di partenza, non la classe di destinazione.

+0

Leggere la tua semplice risposta dopo una buona notte mi ha fatto uscire da 10 ore di difficoltà con questo unire query e criteri roba ...:) – Raindal

6

Ho fatto questo:

public Long getRowCount(CriteriaQuery criteriaQuery,CriteriaBuilder criteriaBuilder,Root<?> root){ 
    CriteriaQuery<Long> countCriteria = criteriaBuilder.createQuery(Long.class); 
    Root<?> entityRoot = countCriteria.from(root.getJavaType()); 
    entityRoot.alias(root.getAlias()); 
    doJoins(root.getJoins(),entityRoot); 
    countCriteria.select(criteriaBuilder.count(entityRoot)); 
    countCriteria.where(criteriaQuery.getRestriction()); 
    return this.entityManager.createQuery(countCriteria).getSingleResult(); 
} 

private void doJoins(Set<? extends Join<?, ?>> joins,Root<?> root_){ 
    for(Join<?,?> join: joins){ 
     Join<?,?> joined = root_.join(join.getAttribute().getName(),join.getJoinType()); 
     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()); 
     doJoins(join.getJoins(),joined); 
    } 
} 

naturalmente non avete bisogno di Root come parametro di ingresso che si potrebbe ottenere da Query criteri,

+0

Bella risposta, grazie per aver condiviso il tuo lavoro. –

3

@ lubo08 dato risposta corretta - complimenti per lui . Ma per due casi d'angolo il suo/la sua codice non funzionerà:

  • Quando le restrizioni di query di criteri utilizzano alias per unisce - allora COUNT richiedono anche questi alias da impostare.
  • Quando uso criteri di query recuperare unirsi [root.fetch(..) invece di root.join(..)]

Così, per completezza ho avuto il coraggio di migliorare il suo/la sua soluzione e presente di seguito:

public <T> long count(final CriteriaBuilder cb, final CriteriaQuery<T> criteria, 
     Root<T> root) { 
    CriteriaQuery<Long> query = createCountQuery(cb, criteria, 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); 
    } 
} 

Anche se è ancora non perfetto, perché solo una radice è onorata.
Ma spero che aiuti qualcuno.

Problemi correlati