How to use JPA Criteria API

The JPA Criteria API is a powerful library, which is well adapted for implementing multi-criteria search functionalities where queries must be built on the fly. For example it can be used to return data in a web search form resulting in cleaner, clearer, more reliable, and more maintainable code.

One of the major advantages of the Criteria API is that it prohibits the construction of queries that are syntactically incorrect.

The Criteria API has been initially added to Hibernate API under the org.hibernate package. Since Hibernate 5.2, the Hibernate Criteria API is deprecated, and new development is done within the JPA Criteria API.

Data Selection

Let’s begin with a simple example to demonstrate the syntax and usage of the Criteria API.

Our Model Class is the following one:

@Entity
@Table 
public class Employee  implements java.io.Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private int id;

    @Column(name = "fistName")
    private String firstName;

    @Column(name = "lastName")
    private String lastName;

    @Column(name = "dept")
    private String dept;

    // Getters Setters and Contructors
}

The following example returns all the Employee in the company with the name of “John Smith”:

@PersistenceContext
private EntityManager em;

public List<Employee> findEmployees() {
    CriteriaBuilder cb = em.getCriteriaBuilder();
    CriteriaQuery criteria = cb.createQuery(Employee.class);
    criteria.from(Employee.class);

    List<Employee> result = em.createQuery(criteria).getResultList();

    return result;
}

Firstly, we have the CriteriaBuilder interface, which we have created through the getCriteriaBuilder() method of the
EntityManager interface.

Please note that if you are using plain Hibernate API, you can fetch the CriteriaBuilder from the Hibernate Session object:

CriteriaBuilder builder = session.getCriteriaBuilder();

The CriteriaBuilder interface is our entrypoint into the Criteria API, acting as a factory for the various objects that we will link together to form a query definition.
The second use of the CriteriaBuilder interface in this example is to construct the conditional expressions in the where clause. All of the conditional expression keywords, operators, and functions from JP QL are represented in some manner on the CriteriaBuilder interface.

Here is how to find the Employee by name:

public List<Employee> findByName(String name) {

    CriteriaBuilder cb = em.getCriteriaBuilder();
    CriteriaQuery<Employee> criteria = cb.createQuery(Employee.class);

    Root<Employee> root = criteria.from(Employee.class);
    criteria.where(cb.equal(root.get("firstName"), name));
    return em.createQuery(criteria).getResultList();
}

Here, the first step is to establish the root of the query by invoking from() to get back a Root object. Roots define the basis from which all joins, paths and attributes are available in the query. In a criteria query, a root is always an entity.

On the other hand, if you want to use multiple conditions (equivalent to the AND in an SQL Query), then you can add extra arguments to the where condition as in the following example:

public List<Employee> findMultipleConditions() {

    CriteriaBuilder cb = em.getCriteriaBuilder();
    CriteriaQuery<Employee> criteria = cb.createQuery(Employee.class);

    Root<Employee> root = criteria.from(Employee.class);

    criteria.select(root).where(cb.like(root.<String>get("firstName"), "J%"),
        // AND
        cb.between(root.get("id"), 1, 2)
           
    );
    return em.createQuery(criteria).getResultList();
}

In the above example, we are fetching all Employee whose name starts with “J” (like “J%) and with id between 1 and 2.

Grouping the Selected Data

You can use the groupBy() and having() methods of the AbstractQuery interface to execute the equivalent of
the GROUP BY and HAVING expressions in a SQL query. Both arguments accept one or more
expressions that are used to group and filter the data.

Let’s see how to group by your Employee using as GROUP BY expression the Department (“dept” field):

public List<Object[]> groupByDepartment() {

    CriteriaBuilder cb = em.getCriteriaBuilder();
    CriteriaQuery<Object[]> criteria = cb.createQuery(Object[].class);

    Root<Employee> root = criteria.from(Employee.class);

    criteria.multiselect(root.get("dept"), cb.count(root));
    criteria.groupBy(root.get("dept"));

    Query q = em.createQuery(criteria);
    List<Object[]> result = q.getResultList();

    result.forEach(item -> System.out.println("Dept : " + item[0] + "\t count : " + item[1]));
    return result;
}

The ResultList of the above query will return a List of Object:

The Object with index 0 contains the Department name (“dept”)

The Object with index 1 contains the count of all Departments for your Employees

You can combine the GROUP BY expression with the HAVING expression. In this example, we are applying the GROUP BY expression only to those Departments that have two or more Employee:

public List<Object[]> groupByDepartmentHaving() {

    CriteriaBuilder cb = em.getCriteriaBuilder();
    CriteriaQuery<Object[]> criteria = cb.createQuery(Object[].class);

    Root<Employee> root = criteria.from(Employee.class);

    criteria.multiselect(root.get("dept"), cb.count(root));
    criteria.groupBy(root.get("dept")).having(cb.ge(cb.count(root),2));

    Query q = em.createQuery(criteria);
    List<Object[]> result = q.getResultList();

    result.forEach(item -> System.out.println("Dept : " + item[0] + "\t count : " + item[1]));
    return result;
}

Predicates

The criteria API also offers a different style of building AND and OR expressions for those who wish
to build things incrementally rather than as a list.

Here is an example of Predicate which uses an Array to store the list of conditions to add to the where statement:

public List<Employee> findUsingPredicate() {
    CriteriaBuilder cb = em.getCriteriaBuilder();
    CriteriaQuery<Employee>  criteria = cb.createQuery(Employee.class);

    Root<Employee> root = criteria.from(Employee.class);
    
    Predicate[] predicates = new Predicate[2];
    predicates[0] = cb.equal(root.get("firstName"), "John");
    predicates[1] = cb.between(root.get("id"), 1, 2);
    
    criteria.select(root).where(predicates);	
    return em.createQuery(criteria).getResultList();
    
}

Finally please note that you can also combine a CriteriaQuery with a TypedQuery. As a matter of fact, the CriteriaQuery<T> is used to programmatically define query, instead of writing it manually, and TypedQuery<T> is used to avoid casting which you have to do when using Query. See this example:

public List<Employee> findUsingPredicateTypeSafe() {
    
    CriteriaBuilder cb = em.getCriteriaBuilder();
    CriteriaQuery<Employee>  criteria = cb.createQuery(Employee.class);

    Root<Employee> root = criteria.from(Employee.class);
    
    Predicate[] predicates = new Predicate[2];
    predicates[0] = cb.equal(root.get("firstName"), "John");
    predicates[1] = cb.between(root.get("id"), 1, 2);
    
    TypedQuery<Employee> q = em.createQuery(criteria); 
    return q.getResultList();	
    
}

Source code: If you want to deploy the above examples to WildFly, the source code is available here: https://github.com/fmarchioni/mastertheboss/tree/master/hibernate/hibernate-criteria

Found the article helpful? if so please follow us on Socials