DEV Community

irshad sheikh
irshad sheikh

Posted on • Originally published at initgrep.com on

Programmatic Criteria Queries using JPA Criteria API

Java Persistence API (JPA) provides the specification of managing data, such as accessing and persisting data, between Java Objects and the databases.

There are obviously many ways in JPA that could be used to interact with a database such as JPQL(Java Persistence Query Language), Criteria API, and Entity specific methods such as persist, merge, remove, flush, etc.

I initially found Criteria API quite intimidating. To be frank, I had to invest quite some time to figure it out. It eventually turned out to be quite a fluent API. I thought why not just write about the experiences. So here it is, “Creating Programmatic Criteria Queries using JPA Criteria API”.

Criteria Queries are type-safe and portable. They are written using Java programming language APIs. They use the abstract schema of the persistent entities to find, modify, and delete persistent entities by invoking JPA Entity Operations.

We will be using the following domain model for building the criteria queries in this tutorial.

Object Model- UMLUML diagram describing the Object Relationship Mapping

We have three Objects( ref. to diagram above ) Student , Course and Passport. Each of the Objects has a relationship with each other:

  • Student has a One-To-One relationship with Passport. It means that Each Student can only have one Passport and vice versa.
  • Student has a One-To-Many relationship with Address which means that a student can have one or more addresses and an address is always assigned to one student.
  • Student has a Many-To-Many relationship with Course which means that Each Student can enroll in many courses and one course can have many students enrolled.

We will begin with a simple Criteria Query and slowly try to build upon it.

public Long getStudentsCount() {

        / **CriteriaBuilder instance** / 
        CriteriaBuilder builder = entityManager.getCriteriaBuilder();

        / **create a criteriaQuery Object** /
        CriteriaQuery<Long> studentQuery = builder.createQuery(Long.class);

        / **create a Root Object -> references to the queried entity** /
        Root<Student> studentRoot = studentQuery.from(Student.class);

        / **Path Object to refer the attribute name of entity** /
        / **we are not using it for now** /
        Path<Object> namePath = studentRoot.get("name");

        / **Aggregate Expression for count operation** /
        Expression<Long> countExpression = builder.count(studentRoot);

        / **** /
        studentQuery.select(countExpression);

        /** instance of Typed Query */
        TypedQuery<Long> typedStudentQuery = 
                            entityManager.createQuery(studentQuery);

        / **return the result** /
        Long count = typedStudentQuery.getSingleResult();

        return count;
    }
Enter fullscreen mode Exit fullscreen mode

The above code example retrieves the total count of students present in the database.

Criteria queries are an Object graph where each part of the graph represents an atomic part of the query. The various steps in building the object graph roughly translate to the following steps.

  • A CriteriaBuilder interface contains all the required methods to build Criteria Queries, Expressions, Ordering, and Predicates.
  • CriteriaQuery interface defines functionality required to build a top-level query. The type specified for criteria query i.e criteriaQuery<Class<T> resultClass> would be the type of the result returned. If no type is provided, the type of result would be Object. Criteria Query contains the methods that specify the item(s) to be returned in the query result, restrict the result based on certain conditions, group results, specify an order for the result, and much more.
  • Root interface represents the root entities involved in the query. There could be multiple roots defined in the Criteria Query.
  • Path interface represents the path to the attribute in the root entity. Path interface also extends to the Expression Interface which contains methods that return Predicates.
  • builder.count() is an aggregate method. It returns an expression that would be used for Selection of the result. When aggregate methods are used as arguments in the select method, the type of the query should match the return type of aggregate method.
  • A TypedQuery instance is required to run the CriteriaQuery.

The final output of the above example is below :)

select
        count(student0_.id) as col_0_0_ 
    from
        student student0_
Enter fullscreen mode Exit fullscreen mode

It seems quite the over work–doesn’t it. The output of so many lines of code is just a plain old SELECT query. But the Criteria API was created to serve a different purpose i.e programmable Query API. It helps to build queries dynamically. You could write just one program to build queries for all objects in your application or build queries depending upon your business logic.

Let’s say, we want to get the count of either Students, Courses or any other entities present in our application. We could either write different queries for each of them or we could only use Criteria API.

public <T> Long getCountOfEntity(Class<T> claz) {

        CriteriaBuilder builder = em.getCriteriaBuilder();
        CriteriaQuery<Long> studentQuery = builder.createQuery(Long.class);
        Root<T> root = studentQuery.from(claz);
        Expression<Long> countExpression = builder.count(root);
        studentQuery.select(countExpression);
        TypedQuery<Long> typedStudentQuery = em.createQuery(studentQuery);

        return typedStudentQuery.getSingleResult();
    }
Enter fullscreen mode Exit fullscreen mode

I have only updated the previous example to allow the generic parameter which specifies the Class of the Entity. The rest of the Code stays the same. You can learn more about Java Generics here.

Let’s look at the above example in detail.

Criteria Query

CriteriaQuery<T> createQuery(Class<T> resultClass);
Enter fullscreen mode Exit fullscreen mode

CreateQuery method takes the Class name of the result type as an argument.

  • If the result is returned as a data-set related to the entity e.g all the students or one of the students. The parameter should be Student.class.
builder.createQuery(Student.class);
Enter fullscreen mode Exit fullscreen mode
  • If the query returns any other result irrespective of which entity it works on, It should have a parameter of the returned type. As we saw above, when the count was returned, the parameter type was Long.class
builder.createQuery(Long.class);
Enter fullscreen mode Exit fullscreen mode

Root

Root<T> root = studentQuery.from(Student.Class)
Enter fullscreen mode Exit fullscreen mode

The Root refers to the entity on which the query would be run such as Student.class in the above example.

Select

studentQuery.select(countExpression);
Enter fullscreen mode Exit fullscreen mode

The select method specifies the result to be returned by the Query. If all the attributes of an entity are supposed to be returned instead of just returning the count, we could pass the root entity as the parameter such as below.

studentQuery.select(studentRoot);
Enter fullscreen mode Exit fullscreen mode

 

If you have come this far, you might also want to read about various methods, Criteria API provides to implement criteria join, Fetch Join, Aggregate functions, and subqueries. I have posted a detailed post about it. Click here to check it out.

Top comments (0)