JPA Criteria Api – A code alternate to raw SQL queries

26 / Jun / 2024 by Sukirti Kaushik 0 comments

Introduction

JPA is a hibernate specification that helps us in managing, accessing & persisting data between RDBMS and Java objects & one of its main features is Criteria API, which is a pre-defined API and was introduced in JPA 2.0. It defines platform-independent criteria queries written in Java and enables us to achieve the same result as a raw query programmatically. It provides us with some object-oriented control over the raw SQL queries. By writing a criteria you define the where clause of a query for your specified domain class.

Basic components

  • CriteriaBuilder — It is a factory class obtained from the entity manager to create various query-related objects such as criteriaQuery, predicates, etc.
  • CriteriaQuery — It represents the structure of the query specifying the “select” clause, “where” clause & ordering of the resultSet.
  • Root — This represents the Entity on which the query will operate i.e. “from” clause of a query.
  • Predicates — This represents the “where” clause of the query.

Steps to Create Criteria Query

  • Create an EntityManagerFactory & EntityManager instance.
  • Create a CriteriaBuilder instance using the session’s getCriteriaBuilder() method.
  • Create a CriteriaQuery instance using criteriaBuilder’s createQuery() method.
  • Once we have created the criteriaQuery instance we need to set the query’s root using its from() method.
  • To get the final result, create a query instance using the session’s createQuery() method by passing criteria query as an argument and then use query.getResultList() or query.getResultSet() method.

Implementing JPA Criteria Query

Define an Entity

import lombok.*;
import javax.persistence.*;

@Entity
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode
public class DemoEmp {
@Id
@GeneratedValue(strategy= GenerationType.AUTO)
private int id;
private String name;
private int salary;
private int experience;
private String departmentName;
}

Define the Criteria Builder

EntityManagerFactory emf = Persistence.createEntityManagerFactory("my-persistence-unit");
EntityManager em = emf.createEntityManager();
CriteriaBuilder criteria = em.getCriteriaBuilder(); 
CriteriaQuery<DemoEmp> cq = criteria.createQuery(DemoEmp .class); 
Root<DemoEmp> root = cq.from(DemoEmp .class); 
cq.select(root); 
Query<DemoEmp> query = em.createQuery(cq);

The above code will get all the rows of the DemoEmp entity from the database.

We can also use CriteriaBuilder for restricting query results based on the conditions using expressions

  • Fetch entities with salary greater than 50000
cq.select(root).where(criteria.gt(root.get("salary"), 50000));
  • Fetch entities having experience between 2 and 5
cq.select(root).where(criteria.between(root.get("experience"), 2, 5));

There are various other methods as well like isNull(), isNotNull(), isEmpty(), isNotEmpty(), in(), like(), aggregate methods etc.

We can also chain these expressions to achieve our desired condition using Predicates.

Predicate greaterThanSalary = criteria.gt(root.get("salary"), 50000);
Predicate experience = criteria.between(root.get("experience"), 2, 5);

Now we can join these 2 conditions with either a Logical “AND” or a Logical “OR” based on our requirement.

cq.select(root).where(criteria.or(greaterThanSalary, experience));
cq.select(root).where(criteria.and(greaterThanSalary, experience));

GroupBy & Having Clause

The AbstractQuery interface of Criteria API has two methods groupBy() and having() which are used to filter the data by grouping them and to set the desired conditions on the grouped data respectively.

CriteriaQuery<DemoEmp> cq = criteria.createQuery(DemoEmp .class); 
Root<DemoEmp> root = cq.from(DemoEmp .class); 
cq.multiselect(root.get("experience"),criteria.count(root)).groupBy(root.get("experience")); 
List<Object[]> results = em.createQuery(cq).getResultList();

In the above code, we are grouping the number of DempEmp based on their experience.

CriteriaQuery<DemoEmp> cq = criteria.createQuery(DemoEmp .class); 
Root<DemoEmp> root = cq.from(DemoEmp .class); 
cq.multiselect(root.get("experience"),criteria.count(root)).groupBy(root.get("experience")).having(criteria.ge(root.get("experience"), 5));  
List<Object[]> results = em.createQuery(cq).getResultList();

In the above code, we are grouping the number of DempEmp having an experience either equal or greater than 5.

CriteriaUpdates

CriteriaUpdates feature allows us to update multiple records in the database using Criteria API. We can create a CriteriaUpdate instance using CriteriaBuilder’s createCriteriaUpdate() method.

This interface has a set() method that enables us to update the existing record with the desired value. We can update multiple attributes & multiple records simultaneously.

CriteriaUpdate<DemoEmp> criteriaUpdate = criteria.createCriteriaUpdate(DemoEmp.class); 
Root<DemoEmp> root = criteriaUpdate.from(DemoEmp.class); 
criteriaUpdate.set("experience", 10); 
criteriaUpdate.set("salary", 1000000); 
criteriaUpdate.where(criteria.equal(root.get("id"), 5))
em.createQuery(criteriaUpdate).executeUpdate();

The above code will update the experience attribute value to 10 & salary attribute value to 100000 for the DemoEmp with id =5

CriteriaDelete

CriteriaDelete feature allows us to delete multiple records from the database using Criteria API. We can create a CriteriaDelete instance using CriteriaBuilder’s createCriteriaDelete() method. We can delete multiple records by providing a where clause for restrictions.

CriteriaDelete<DemoEmp> criteriaDelete = criteria.createCriteriaDelete(DemoEmp.class);
Root<DemoEmp> root = criteriaDelete.from(DemoEmp.class); 
criteriaDelete.where(criteria.equal(root.get("departmentName"), "Research")); 
em.createQuery(criteriaDelete).executeUpdate();

The above code will delete all the employees of the “Research” department.

Criteria Join

JPA provides a Join interface for performing joins for retrieving data from various interconnected database tables. We can perform multiple joins within a single query as well. To understand this in detail let’s change our entity and add some relationships to practice joins.

Updated Entity

import lombok.*;
import javax.persistence.*; 
import java.util.List;

@Entity 
@Getter 
@Setter 
@NoArgsConstructor 
@AllArgsConstructor 
@Builder 
@EqualsAndHashCode 
public class DemoEmp { 
@Id 
@GeneratedValue(strategy= GenerationType.AUTO) 
private int id; 
private String name; 
private int salary; 
private int experience; 
private String departmentName;

@ManyToMany(fetch = FetchType.LAZY)
@JoinTable(name = "demo_emp_project_mapping",
joinColumns = {@JoinColumn(name = "demo_emp_id")},
inverseJoinColumns = {@JoinColumn(name = "project_id")}
)
@JsonBackReference
private List<Project> projects;

@ElementCollection
private List<String> certifications;
}
import lombok.*;
import javax.persistence.*;

@Entity 
@Getter 
@Setter 
@NoArgsConstructor 
@AllArgsConstructor 
@EqualsAndHashCode 
public class Project {
@Id 
@GeneratedValue(strategy= GenerationType.AUTO) 
private int id; 
private String name;

@ManyToMany(mappedBy = "projects")
@JsonBackReference
private List<DemoEmp> employees;
}
CriteriaQuery<DemoEmp> cq = criteria.createQuery(DemoEmp .class); 
Root<DemoEmp> root = cq.from(DemoEmp .class); 
List<Predicate> conditions = new ArrayList();

ListJoin<DemoEmp, String> empCertificationJoin = root.joinList("certifications");
conditions.add(criteria.equal(empCertificationJoin, "XYZ_Certification"));

Join<DemoEmp, Project> empProjectJoin = root.join("projects");
conditions.add(criteria.equal(empProjectJoin.get("id"), 1));

cq.orderBy( criteria.asc(root.get("salary")), criteria.desc(root.get("experience")));
cq.select(root).where(conditions.toArray(new Predicate[]{})).distinct(true);

List<DempEmp> finalOutput = em.createQuery(cq).getResultList;

The above code will retrieve all the employees having “XYZ_Certification” and worked on the project with id “1“. CriteriaQuery’s “where(conditions.toArray(new Predicate[]{}))” combines all the predicates present in the list in a logical “and“.

Benefits of using Criteria Queries

  • Dynamic Query Building — Nice, clean & object-oriented API dynamic code
  • Type-Safety — It has the type-safety benefits of Java.
  • Metamodel Integration — Compile time error detection.
  • Refactoring Support — Easy to modify as it provides better support for dynamic queries as compared to HQL & JPQL.

Conclusion

JPA Criteria Query API is a powerful feature that allows you to write a clean, object-oriented, type-safe code. The CriteriaUpdate & CriteriaDelete introduced in JPA 2.1 make JPA Criteria API a better solution by providing the condition-specific bulk update & delete functionality that was missing in the earlier version. Criteria Joins provides us precision, flexibility, and efficiency as we can define complex conditions for merging data using multiple criteria and logical operators. Using criteria for joins helps us ignore the unnecessary matches and reduces the dataset size which helps to improve performance while dealing with large datasets.

FOUND THIS USEFUL? SHARE IT

Leave a Reply

Your email address will not be published. Required fields are marked *