1. Overview
In this tutorial, we’ll see how to use JPA and Hibernate queries and the difference between Criteria, JPQL, and HQL queries. Criteria queries enable the user to write queries without using raw SQL. Along with Criteria queries, we’ll explore writing Hibernate Named Queries and how to use the @Query annotation in Spring Data JPA.
Before we dive in, we should note that the Hibernate Criteria API has been deprecated since Hibernate 5.2. Therefore, we’ll be using the JPA Criteria API in our examples, as it’s the new and preferred tool for writing Criteria queries. So, from here on, we’ll refer to it simply as the Criteria API.
2. Criteria Queries
The Criteria API helps in building the Criteria query object by applying different filters and logical conditions on top of it. This is an alternate way to manipulate objects and return the desired data from an RDBMS table.
The createCriteria() method from the Hibernate Session returns the persistence object instance for running a criteria query in the application. Simply put, the Criteria API builds up a criteria query that applies different filters and logical conditions.
2.1. Maven Dependencies
Let’s grab the latest version of the reference JPA dependency – which implements JPA in Hibernate – and add it to our pom.xml:
<dependency>
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-core</artifactId>
<version>6.4.2.Final</version>
</dependency>
2.2. Using the Criteria Queries and Expressions
As per the user’s conditions, CriteriaBuilder controls the query results. It uses the where() method from CriteriaQuery, which provides CriteriaBuilder expressions.
Let’s look at the entity we’ll be using in this article:
public class Employee {
private Integer id;
private String name;
private Long salary;
// standard getters and setters
}
Let’s look at a simple criteria query that will retrieve all the rows of “Employee” from the database:
Session session = HibernateUtil.getHibernateSession();
CriteriaBuilder cb = session.getCriteriaBuilder();
CriteriaQuery<Employee> cr = cb.createQuery(Employee.class);
Root<Employee> root = cr.from(Employee.class);
cr.select(root);
Query<Employee> query = session.createQuery(cr);
List<Employee> results = query.getResultList();
session.close();
return results;
The above Criteria query returns a set of all the items. Let’s see how it happens:
- The SessionFactory object creates the Session instance
- The Session returns an instance of CriteriaBuilder using the getCriteriaBuilder() method
- The CriteriaBuilder uses the createQuery() method. This creates the CriteriaQuery() object that further returns the Query instance
- In the end, we call the getResult() method to obtain the query object that holds the results
Let’s look at another expression from CriteriaQuery:
cr.select(root).where(cb.gt(root.get("salary"), 50000));
For its result, the above query returns the set of employees having a salary of more than 50000.
3. JPQL
JPQL stands for Java Persistence Query Language. Spring Data provides multiple ways to create and execute a query, and JPQL is one of these. It defines queries using the @Query annotation in Spring to execute both JPQL and native SQL queries. The query definition uses JPQL by default.
We use the @Query annotation to define a SQL query in Spring. Any query defined by the @Query annotation has higher priority over named queries, which are annotated with @NamedQuery.
3.1. Using JPQL Queries
Let’s build a dynamic query using JPQL:
@Query(value = "SELECT e FROM Employee e")
List<Employee> findAllEmployees(Sort sort);
With JPQL queries that have argument parameters, Spring Data passes the method arguments to the query in the same order as the method declaration. Let’s look at a couple of examples that pass method arguments into the query:
@Query("SELECT e FROM Employee e WHERE e.salary = ?1")
Employee findAllEmployeesWithSalary(Long salary);
@Query("SELECT e FROM Employee e WHERE e.name = ?1 and e.salary = ?2")
Employee findEmployeeByNameAndSalary(String name, Long salary);
For the latter query above, the name method argument is passed as the query parameter with respect to index 1, and the salary argument is passed as the index 2 query parameter.
3.2. Using the JPQL Native Queries
We can execute these SQL queries directly in our databases using native queries, which refer to real databases and table objects. We need to set the value of the nativeQuery attribute to true for defining a native SQL query. The native SQL query will be defined in the value attribute of the annotation.
Let’s see a native query that shows an indexed parameter to be passed as an argument for the query:
@Query(
value = "SELECT * FROM Employee e WHERE e.salary = ?1",
nativeQuery = true)
Employee findEmployeeBySalaryNative(Long salary);
Using Named Parameters makes the query easier to read and less error-prone in the case of refactoring. Let’s see an illustration of a simple Named Query in JPQL and native format:
@Query("SELECT e FROM Employee e WHERE e.name = :name and e.salary = :salary")
Employee findEmployeeByNameAndSalaryNamedParameters(
@Param("name") String name,
@Param("salary") Long salary);
The method parameters are passed to the query using named parameters. We can define named queries by using the @Param annotation inside the repository method declaration. As a result, the @Param annotation must have a string value that matches the corresponding JPQL or SQL query name.
@Query(value = "SELECT * FROM Employee e WHERE e.name = :name and e.salary = :salary",
nativeQuery = true)
Employee findUserByNameAndSalaryNamedParamsNative(
@Param("name") String name,
@Param("salary") Long salary);
4. HQL
HQL stands for Hibernate Query Language. It’s an object-oriented language similar to SQL that we can use to query our database. However, the main disadvantage is the code’s unreadability. We can define our queries as Named Queries to place them in the actual code that accesses the database.
4.1. Using Hibernate Named Query
A Named Query defines a query with a predefined, unchangeable query string. These queries are fail-fast since they’re validated during the creation of the session factory. Let’s define a Named Query using the org.hibernate.annotations.NamedQuery annotation:
@NamedQuery(name = "Employee_FindByEmployeeId",
query = "from Employee where id = :id")
Each @NamedQuery annotation attaches itself to one entity class only. We can use the @NamedQueries annotation to group more than one named query for an entity:
@NamedQueries({
@NamedQuery(name = "Employee_findByEmployeeId",
query = "from Employee where id = :id"),
@NamedQuery(name = "Employee_findAllByEmployeeSalary",
query = "from Employee where salary = :salary")
})
4.2. Stored Procedures and Expressions
In conclusion, we can use the @NamedNativeQuery annotation for storing the procedures and functions:
@NamedNativeQuery(
name = "Employee_FindByEmployeeId",
query = "select * from employee emp where id=:id",
resultClass = Employee.class)
5. Advantages of Criteria Queries Over HQL and JPQL Queries
The main advantage of Criteria Queries over HQL is the nice, clean, object-oriented API. As a result, we can detect errors in Criteria API during the compile time.
In addition, JPQL queries and Criteria queries have the same performance and efficiency.
Criteria queries are more flexible and provide better support for writing dynamic queries as compared to HQL and JPQL.
But HQL and JPQL provide native query support that isn’t possible with the Criteria queries. This is one of the disadvantages of the Criteria query.
We can easily write complex joins using JPQL native queries**,** whereas it gets difficult to manage while applying the same with Criteria API.
6. Conclusion
In this article, we mainly looked at the basics of Criteria queries, JPQL queries, and the HQL queries in Hibernate and JPA. In addition, we learned how to use these queries and the advantages and disadvantages of each approach.
As always, the complete code examples used in this article can be found over on GitHub and here.