Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JPA 2.0: count for arbitrary CriteriaQuery?

I am trying to implement the following convenience method:

/**
 * 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, this is done by

criteria.setProjection(Projections.rowCount());

What is the equivalent to the above in JPA? I found numerous simple count examples, but none of them made use of a CriteriaQuery whose row count should be determined.

EDIT:

I unfortunately found out that @Pascal's answer is not the correct one. The problem is very subtle and only shows up when you use joins:

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

When calling findCountByCriteria(query), it dies with the following exception:

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

Is there any other way to provide such a CountByCriteria method?

like image 750
blubb Avatar asked Oct 22 '10 14:10

blubb


2 Answers

I wrote a utility class, JDAL JpaUtils to do it:

  • count results: Long count = JpaUtils.count(em, criteriaQuery);
  • copy CriteriaQueries: JpaUtils.copyCriteria(em, criteriaQueryFrom, criteriaQueryTo);
  • get count criteria: CriteriaQuery<Long> countCriteria = JpaUtils.countCriteria(em, criteria)

and so on...

If you are interested in the source code, see JpaUtils.java

like image 85
Jose Luis Martin Avatar answered Nov 10 '22 14:11

Jose Luis Martin


None of the above solutions work for EclipseLink 2.4.1, they all ended with a count on a Cartesian product (N^2), here is a small hack for EclipseLink, the only drawback is that I don't know what will happen if you are selecting FROM more than one Entity, it will try to count from the 1st found Root of your CriteriaQuery, this solution DOESN'T work for Hibernate though (JDAL does, but JDAL doesn't work for 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();
  }
like image 37
Guido Medina Avatar answered Nov 10 '22 15:11

Guido Medina