web-dev-qa-db-fra.com

Nombre total de lignes pour la pagination à l'aide de l'API JPA Criteria

J'implémente une fonctionnalité de type "Recherche avancée" pour une entité dans mon système de telle sorte que l'utilisateur puisse rechercher cette entité en utilisant plusieurs conditions (eq, ne, gt, lt, etc.) sur les attributs de cette entité. J'utilise l'API Criteria de JPA pour générer dynamiquement la requête Criteria, puis j'utilise setFirstResult() & setMaxResults() pour prendre en charge la pagination. Tout allait bien jusqu'à présent, mais maintenant je veux afficher le nombre total de résultats sur la grille de résultats, mais je n'ai pas vu de méthode simple pour obtenir le nombre total de requêtes de critères.
Voici à quoi ressemble mon code:

CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<Brand> cQuery = builder.createQuery(Brand.class);
Root<Brand> from = cQuery.from(Brand.class);
CriteriaQuery<Brand> select = cQuery.select(from);
.
.
//Created many predicates and added to **Predicate[] pArray**
.
.
select.where(pArray);
// Added orderBy clause
TypedQuery typedQuery = em.createQuery(select);
typedQuery.setFirstResult(startIndex);
typedQuery.setMaxResults(pageSize);
List resultList = typedQuery.getResultList();

Mon jeu de résultats pourrait être gros, donc je ne veux pas charger mes entités pour la requête de comptage, alors dites-moi un moyen efficace d'obtenir le nombre total comme la méthode rowCount() sur les critères (je pense que c'est là dans les critères d'Hibernate).

24
ThinkFloyd

Merci Vladimir! J'ai pris votre idée et utilisé une requête de comptage distincte pour utiliser mon tableau existant de prédicats. L'implémentation finale ressemble à ceci:

CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<Brand> cQuery = builder.createQuery(Brand.class);
Root<Brand> from = cQuery.from(Brand.class);
CriteriaQuery<Brand> select = cQuery.select(from);
.
.
//Created many predicates and added to **Predicate[] pArray**
.
.
CriteriaQuery<Long> cq = builder.createQuery(Long.class);
cq.select(builder.count(cq.from(Brand.class)));
// Following line if commented causes [org.hibernate.hql.ast.QuerySyntaxException: Invalid path: 'generatedAlias1.enabled' [select count(generatedAlias0) from xxx.yyy.zzz.Brand as generatedAlias0 where ( generatedAlias1.enabled=:param0 ) and ( lower(generatedAlias1.description) like :param1 )]]
em.createQuery(cq);
cq.where(pArray);
Long count = em.createQuery(cq).getSingleResult();
.
.
select.where(pArray);
.
.
// Added orderBy clause
TypedQuery typedQuery = em.createQuery(select);
typedQuery.setFirstResult(startIndex);
typedQuery.setMaxResults(pageSize);
List resultList = typedQuery.getResultList()

Bien que cela fonctionne bien, mais je ne sais toujours pas pourquoi je dois écrire

em.createQuery(cq);

pour le faire fonctionner. Une idée?

29
ThinkFloyd

Pourquoi n'utilisez-vous pas simplement le comptage?

CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<Long> cQuery = builder.createQuery(Long.class);
Root<Brand> from = cQuery.from(Brand.class);
CriteriaQuery<Long> select = cQuery.select(builder.count(from));
.
.
//Created many predicates and added to **Predicate[] pArray**
.
.
select.where(pArray);
// Added orderBy clause
TypedQuery<Long> typedQuery = em.createQuery(select);
typedQuery.setFirstResult(startIndex);
//typedQuery.setMaxResults(pageSize);
// here is the size of your query 
Long result = typedQuery.getSingleResult();
14
Vladimir Ivanov

Si vous utilisez Hibernate comme fournisseur JPA, jetez un œil à projections , en particulier Projections.rowCount().

Cependant, vous devrez peut-être exécuter la requête deux fois, obtenez d'abord le nombre, puis les résultats.

Notez que pour JPA ordinaire, vous pourriez avoir besoin d'une autre approche.

2
Thomas

Je suppose que les deux réponses fonctionnent. Mais aucun d'entre eux n'est optimal. Le problème avec ThinkFloyd est que createQuery est utilisé deux fois. Et Vladimir Ivanov a créé deux instances de CriteriaQuery qui, à mon avis, ne sont pas nécessaires.

val cb = entityManager.criteriaBuilder
val cq = cb.createQuery(ManualQuery::class.Java)
val manualQuery = cq.from(ManualQuery::class.Java)

val predicates = ArrayList<Predicate>()

/*
predications..... 
*/ 

cq.select(manualQuery)
        .where(*predicates.toTypedArray())
        .orderBy(cb.desc(manualQuery.get<ZonedDateTime>("createdDate")))

val query = entityManager.createQuery(cq)

// total rows count
val count = query.resultList.size

val indexedQuery = query
        .setFirstResult((currentPage - 1) * pageSize)
        .setMaxResults(itemsPerPage)

Faire ça marche. Et cela se fait en Kotlin. Vous le faites de la même manière dans Java.

0
mahan