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.
UML 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 aOne-To-One
relationship withPassport
. It means that Each Student can only have one Passport and vice versa. -
Student
has aOne-To-Many
relationship withAddress
which means that a student can have one or more addresses and an address is always assigned to one student. -
Student
has aMany-To-Many
relationship withCourse
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;
}
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.ecriteriaQuery<Class<T> resultClass>
would be the type of the result returned. If no type is provided, the type of result would beObject
. 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 theroot
entity.Path
interface alsoextends
to theExpression
Interface which contains methods that return Predicates. -
builder.count()
is an aggregate method. It returns an expression that would be used forSelection
of the result. When aggregate methods are used as arguments in theselect
method, the type of the query should match the return type of aggregate method. - A
TypedQuery
instance is required to run theCriteriaQuery
.
The final output of the above example is below :)
select
count(student0_.id) as col_0_0_
from
student student0_
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();
}
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);
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);
- 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);
Root
Root<T> root = studentQuery.from(Student.Class)
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);
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);
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)