30

I am implementing "Advanced Search" kind of functionality for an Entity in my system such that user can search that entity using multiple conditions(eq,ne,gt,lt,like etc) on attributes of this entity. I am using JPA's Criteria API to dynamically generate the Criteria query and then using setFirstResult() & setMaxResults() to support pagination. All was fine till this point but now I want to show total number of results on results grid but I did not see a straight forward way to get total count of Criteria query.
This is how my code looks like:

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

My result set could be big so I don't want to load my entities for count query, so tell me efficient way to get total count like rowCount() method on Criteria (I think its there in Hibernate's Criteria).

mahan
  • 12,366
  • 5
  • 48
  • 83
ThinkFloyd
  • 4,981
  • 6
  • 36
  • 56

4 Answers4

33

Thanks Vladimir! I took your idea and used separate count query to use my existing array of predicates in it. Final implementation looks like this:

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

Though this is working fine but still I am not sure why I have to write

em.createQuery(cq);

to get it working. Any Idea?

ThinkFloyd
  • 4,981
  • 6
  • 36
  • 56
  • Were you able to find out why exactly you need to add the `em.createQuery(cq);`? Thanks – Ittai Dec 22 '11 at 07:01
  • Hey, this is just what I needed... bu I would also like to know why this line is neccessary !? – Pierre Henry Feb 28 '13 at 14:15
  • 1
    I suspect it is to do with the fact that the `Predicate`s are being built using the `Root<>` from this part `Root from = cQuery.from(Brand.class);` and are then used for both the count query and the paged query. AFAIK you need to build the predicates twice using both `Root<>` objects. The second one is hidden in this line `cq.select(builder.count(cq.from(Brand.class)));`. Why exactly `em.createQuery(cq);` works I don't know, IMO it should throw an exception. – Neilos Nov 09 '17 at 22:43
  • Why don't we just get it from resultList.size() ? – Nan Aug 24 '21 at 21:53
16

Why don't you just use count?

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();
flash
  • 6,730
  • 7
  • 46
  • 70
Vladimir Ivanov
  • 42,730
  • 18
  • 77
  • 103
2

If you're using Hibernate as your JPA-Provider have a look at projections, especially Projections.rowCount().

You might have to execute the query twice though, first get the count then get the results.

Note that for plain JPA you might need some other approach.

Thomas
  • 87,414
  • 12
  • 119
  • 157
  • 3
    -1. The OP quite clearly stated he's looking for ansers with respect to "JPA Criteria API", don't you think your answer is somewhat misleading? (Implying the hibernate projections is the way to do this). I'm saying this because when I stumbled on to the question I saw your answer first and it confused me into thinking that's the way (especially since there was an add between you and the next answer which made you the only visible answer). – Ittai Dec 21 '11 at 06:54
  • I think that when you read the question you see that it's not about hibernate, It's just that a lot of times in SO you see that people answer what they think the OP needs and not what the OP requests. Since it seems like an honest mistake if you'll edit your question I'll change my vote. – Ittai Dec 21 '11 at 09:10
0

I guess both of the answers work. But none of them is optimal. The problem with ThinkFloyd's answer is that createQuery is used two times. And Vladimir Ivanov has created two instances of CriteriaQuery which I think is unnecessary.

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)

Doing go it works. And it is done in Kotlin. You do it the same way in Java.

mahan
  • 12,366
  • 5
  • 48
  • 83
  • 3
    The downside to this is that you get the full result set just to count the rows, then you get the paged results afterwards. If you have to do two separate queries, it's better to just select the count then get the paginated results. – jason Mar 23 '21 at 21:41
  • @jason could you add your solution? – mahan Mar 24 '21 at 10:48