One of the main reasons for the popularity of the Java Persistence API (JPA) was JPQL, which supported an object-oriented mechanism for querying the database. But JPQL had a major limitation: JPQL queries were constructed as query strings, which were not evaluated at the compile time. The JPA 2.0 release introduced the concept of criteria queries, typesafe and more object oriented queries. Using criteria queries, developers can check the correctness of the queries at the compile time. This feature was previously available only in certain proprietary frameworks such as Hibernate.
In this article I explain criteria queries and explore the concept of metamodel, which is essential for building the criteria queries. The various APIs for criteria queries are also discussed.
The Metamodel Concept
In JPA 2.0, criteria queries are based on the concept of metamodel, which is defined for managed entities (be they entity classes, embedded classes or mapped super classes) of a specific persistence unit. In short: A class that provides meta information about a managed entity is a metamodel class. The static metamodel class that describes the state and the relationship of a managed class can be (1) generated using an annotation processor, (2) created by the programmer or (3) accessed using an EntityManager
.
Consider an Employee
entity defined in the package com.demo.entities
. Suppose this entity has primitive attributes such as id
, name
and age
and an OneToMany
association with the Address class as shown below.
package com.demo.entities;@Entity@Tablepublic class Employee{ private int id; private String name;private int age;@OneToManyprivate List<Address> addresses;// Other code…}
The name of the canonical metamodel class for Employee
(defined in the com.demo.entities
package) will be Employee_
annotated with the annotation javax.persistence.StaticMetamodel
. The properties of the metamodel classes are all static and public. Every attribute of the Employee
entity will be mapped in the respective metamodel class using the following rules as described in the JPA 2.0 specification:
- For a non-collection type such as the
id
,name
andage
properties ofEmployee
, a static propertySingularAttribute<A, B> b
is defined, whereb
is an object of typeB
defined in classA
. - For a collection type such as
addresses
defined incom.entities.Employee
, a static property of typeListAttribute<A, B> b
is defined, where theList
objectb
is of typeB
defined in classA
. The other collection types can be ofSetAttribute
,MapAttribute
orCollectionAttribute
type.
Here is the generated metamodel class using the annotation processor:
package com.demo.entities;import javax.annotation.Generated;import javax.persistence.metamodel.SingularAttribute;import javax.persistence.metamodel.ListAttribute;import javax.persistence.metamodel.StaticMetamodel;@Generated("EclipseLink-2.1.0.v20100614-r7608 @ Tue Jul 27 10:13:02 IST 2010")@StaticMetamodel(Employee.class)public class Employee_ { public static volatile SingularAttribute<Employee, Integer> id; public static volatile SingularAttribute<Employee, Integer> age; public static volatile SingularAttribute<Employee, String> name; public static volatile ListAttribute<Employee, Address> addresses;}
As their name implies, annotation processors process annotations and help in creating source files. Annotation processing can be activated at compile time. The metamodel classes created follow the rules for defining a canonical metamodel class as described in the JPA 2.0 specification. The NetBeans IDE (NetBeans 6.8) currently does not support annotation processing, but the Eclipse IDE and other build tools such as Maven and Ant do.
The biggest advantage of using a canonical metamodel class is that through its instantiation the persistent properties of an entity (the Employee
entity in this case) can be accessed at compile time. This feature makes criteria queries more typesafe at compile time itself.
The metamodel API is very closely related to the standard reflection API in Java. The major difference is that the complier cannot verify its correctness. For example, the following code would pass the compilation test:
Class myClass = Class.forName("com.demo.Test");Field myField = myClass.getField("myName");
The compiler assumes that the property named myName
is defined in the com.demo.Test
package, but it throws a run time exception if the class does not define a property called myName
.
The metamodel API forces the compiler to check whether the appropriate values are assigned to a persistent property of the Entity class. For instance, consider the age
property of the Employee
class, which is an integer
variable. The compiler throws an error if that property is assigned with a String
value. The implementers are not required to support the non-canonical feature because they are not portable across different vendors. Metamodel classes that are written by the programmers are generally called non-canonical metamodel classes. The persistence provider initializes the properties of the metamodel class when the EntityManagerFactory
is created.
Working with Criteria Queries
To better understand criteria queries, consider a Dept
entity that has a collection of Employee
instances. The metamodel classes for Employee
and the Dept
entity are as shown below:
//All Necessary Imports@StaticMetamodel(Dept.class)public class Dept_ { public static volatile SingularAttribute<Dept, Integer> id; public static volatile ListAttribute<Dept, Employee> employeeCollection; public static volatile SingularAttribute<Dept, String> name;}//All Necessary Imports@StaticMetamodel(Employee.class)public class Employee_ { public static volatile SingularAttribute<Employee, Integer> id; public static volatile SingularAttribute<Employee, Integer> age; public static volatile SingularAttribute<Employee, String> name; public static volatile SingularAttribute<Employee, Dept> deptId;}
The following code snippet shows a criteria query, which is used to obtain all the employee instances whose age is greater than 24.
CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder();CriteriaQuery<Employee> criteriaQuery = criteriaBuilder.createQuery(Employee.class);Root<Employee> employee = criteriaQuery.from(Employee.class);Predicate condition = criteriaBuilder.gt(employee.get(Employee_.age), 24);criteriaQuery.where(condition);TypedQuery<Employee> typedQuery = em.createQuery(criteriaQuery);List<Employee> result = typedQuery.getResultList();Corresponding SQL: SELECT * FROM employee WHERE age > 24
Building a CriteriaQuery Instance
A CriteriaQuery
object is required for working with criteria queries on entity types or embeddable types. A CriteriaQuery
object is obtained by invoking the CriteriaBuilder
, createQuery
or CriteriaBuilder.createTupleQuery
methods, where the CriteriaBuilder
object acts as the factory for CriteriaQuery
objects. A CriteriaBuilder
factory object is obtained by either invoking the EntityManager.getCriteriaBuilder
method or the EntityManagerFactory.getCriteriaBuilder
instance. Generally, a CriteriaQuery
object is a typed object that specifies the type of outcome to be obtained from executing the criteria query. A CriteriaQuery
object for the Employee
entity is created in the following way:
CriteriaBuilder criteriaBuilder = emf.getCriteriaBuilder();CriteriaQuery<Employee> criteriaQuery = cb.createQuery(Employee.class);
QueryRoot
AbstractQuery
is the superclass for the CriteriaQuery
interface. It provides methods for obtaining the root of the query. The query root of a criteria query defines the entity type, which can be further navigated to obtain the desired results. It is analogous to the FROM
clause of a SQL query.
A Root
instance is also typed and defines the type that can appear in the FROM
clause of the query. A query root instance can be obtained using the AbstractQuery.from
method by passing an EntityType
argument. A criteria query can also have multiple query roots. The Root
object for an Employee
entity type can be created using the following syntax.
Root<Employee> employee = criteriaQuery.from(Employee.class);
Filtering the Queries
Filtering conditions are applied to the SQL statements in the FROM
clause. In criteria queries, filtering conditions are applied to a CriteriaQuery
object through a Predicate
or an Expression
instance. These conditions to a criteria query object are applied using the CriteriaQuery .where
method. A CriteriaBuilder
object also acts as a factory for Predicate
instances. A Predicate
instance is created by invoking conditional methods of the CriteriaBuilder
such as equal
, notEqual
, gt
, ge
, lt
, le
, between
and like
. A Predicate
instance can also be created by invoking the isNull
, isNotNull
and in
methods of the Expression
instance. A compound predicate statement can be constructed using the and
, or
and not
methods of the CriteriaBuilder
.
The following code snippet shows a Predicate
instance that checks for the employee instances whose ages are greater than 24.
Predicate condition = criteriaBuilder.gt(employee.get(Employee_.age), 24);criteriaQuery.where(condition);
The age
property is accessed using the Employee_
metamodel class at compile time using Employee_.age
, which is called as a path expression. The compiler throws an error if the age
property is compared with a String
literal, which was not possible in JPQL.
Executing a Query and Obtaining a Metamodel Instance
An executable query instance is created when the EntityManager.createQuery(CriteriaQuery)
method is invoked, which returns a TypedQuery
object that specifies the actual type returned from the criteria query. The TypedQuery
interface is a sub type of the javax.persistence.Query
interface. In this snippet, the type information specified in the TypedQuery
object is Employee
and the query gets executed when the getResultList
method is invoked.
TypedQuery<Employee> typedQuery = em.createQuery(criteriaQuery);List<Employee> result = typedQuery.getResultList();
A metamodel instance is obtained by invoking the EntityManager.getMetamodel
method. A metamodel instance of type EntityType<Employee>
is then obtained by invoking Metamodel.entity(Employee.class)
, which is passed to the CriteriaQuery.from
to obtain the query root.
Metamodel metamodel = em.getMetamodel();EntityType<Employee> Employee_ = metamodel.entity(Employee.class);Root<Employee> empRoot = criteriaQuery.from(Employee_);
It is also possible to obtain metamodel information by invoking the Root.getModel
method. The Dept_
instance of type EntityType<Dept>
is obtained and the attribute name
is obtained by invoking the getSingularAttribute
method, which is compared with a string literal.
CriteriaQuery criteriaQuery = criteriaBuilder.createQuery();Root<Dept> dept = criteriaQuery.from(Dept.class);EntityType<Dept> Dept_ = dept.getModel();Predicate testCondition = criteriaBuilder.equal(dept.get(Dept_.getSingularAttribute("name", String.class)), "Ecomm");Corresponding SQL: SELECT * FROM dept WHERE name = 'Ecomm'
Expressions
An Expression object is used in a SELECT
, WHERE
or HAVING
clause of a SQL statement. The Expression interface has isNull
, isNotNull
and in
methods. The following code snippet shows the usage of Expression.in
, where the employee age is checked to be either 20 or 24.
CriteriaQuery<Employee> criteriaQuery = criteriaBuilder .createQuery(Employee.class);Root<Employee> employee = criteriaQuery.from(Employee.class);criteriaQuery.where(employee.get(Employee_.age).in(20, 24));em.createQuery(criteriaQuery).getResultList();Corresponding SQL: SELECT * FROM employee WHERE age in (20, 24)
An Expression object can be obtained using the equal
, notequal
, gt
, ge
, le
, lt
, between
, like
methods of the CriteriaBuilder interface. The following code snippets show the usage of the like
and between
methods. The CriteriaBuilder.between
method checks if the age
is in a specified range, while the CriteriaBuilder.like
method checks if the name
property matches the specified pattern.
// Usage of CriteriaBuilder.betweencriteriaQuery.where(criteriaBuilder.between(employee.get(Employee_.age), 24, 25));em.createQuery(criteriaQuery).getResultList();Corresponding SQL: SELECT * FROM employee WHERE age between 24 AND 25//Usage of CriteriaBuilder.likecriteriaQuery.where(criteriaBuilder.like(employee.get(Employee_.name), "N%"));em.createQuery(criteriaQuery).getResultList();Corresponding SQL: SELECT * FROM employee WHERE name like N%
Compound Predicates
Criteria queries also allow the developers to write compound predicates, through which queries can be tested for multiple conditions. The following query checks for two conditions: first, whether the name
property starts with M and second, whether the age
of the employee is 25. A logical AND
operation is performed to get the resultant record(s).
criteriaQuery.where(criteriaBuilder.and(criteriaBuilder.like(employee.get(Employee_.name), "M%"), criteriaBuilder.equal(employee.get(Employee_.age), 25)));em.createQuery(criteriaQuery).getResultList();Corresponding SQL: SELECT * FROM employee WHERE name LIKE 'M%' AND age = 25
Join Queries
In SQL, joins are performed to browse through multiple tables and obtain query results. Similarly, entity joins are performed by invoking the From.join
method. Joins help in navigating from one entity to another to obtain criteria query results.
The join
method on the Root returns a Join<Dept, Employee>
type (it can also be of SetJoin
, ListJoin
, MapJoin
or CollectionJoin
type), where the Dept
object represents the source from which the target Employee
object is obtained. By default, join operations use inner joins, and outer joins can be used by specifying the JoinType
argument as LEFT
or RIGHT
in the join
method.
CriteriaQuery<Dept> cqDept = criteriaBuilder.createQuery(Dept.class);Root<Dept> deptRoot = cqDept.from(Dept.class);Join<Dept, Employee> employeeJoin = deptRoot.join(Dept_.employeeCollection);cqDept.where(criteriaBuilder.equal(employeeJoin.get(Employee_.deptId).get(Dept_.id), 1));TypedQuery<Dept> resultDept = em.createQuery(cqDept);Corresponding SQL: SELECT * FROM employee e, dept d WHERE e.deptId = d.id and d.id = 1
Fetch Joins
Fetch joins are very helpful in optimizing data access when a collection valued property is involved. This is achieved by pre-fetching the associated object and reducing the overhead of lazy loading. With criteria queries, the fetch
method is used to specify the associated property that needs to be fetched as part of the query execution instead of the join
method.
The semantics of the Fetch
join are the same as that of the Join
. Because the result of the Fetch
operation does not return a Path
object, it cannot be further referenced in the query. In the example below, the employeeCollection
object is loaded when the Dept object is queried and there is no second call to the database as with lazy loading.
CriteriaQuery<Dept> d = cb.createQuery(Dept.class);Root<Dept> deptRoot = d.from(Dept.class);deptRoot.fetch("employeeCollection", JoinType.LEFT);d.select(deptRoot);List<Dept> dList = em.createQuery(d).getResultList();Corresponding SQL: SELECT * FROM dept d, employee e WHERE d.id = e.deptId
Path Expression
Objects, such as Root
instances or Join
instances or an instance obtained from another Path
object using the get
method, can qualify for a Path
object. A Path
object is essential when the query needs to navigate to the attributes of the entities. The argument that the get
method takes is the attribute specified in the entity’s metamodel class. The Path
objects are used normally in select
or where
methods of the criteria query object, like this for example:
CriteriaQuery<String> criteriaQuery = criteriaBuilder.createQuery(String.class);Root<Dept> root = criteriaQuery.from(Dept.class);criteriaQuery.select(root.get(Dept_.name));
Parameterized Expressions
In JPQL, query parameters were passed at run time by using the named parameter syntax (variable preceded by a colon, e.g. :age
). In criteria queries, the query parameters can be passed at runtime by creating a typed ParameterExpression
object and setting it with a value before querying using the TypedQuery.setParameter
method. The following code snippet shows a ParameterExpression age
of type Integer
, which is set to a value of 24.
ParameterExpression<Integer> age = criteriaBuilder.parameter(Integer.class);Predicate condition = criteriaBuilder.gt(testEmp.get(Employee_.age), age);criteriaQuery.where(condition);TypedQuery<Employee> testQuery = em.createQuery(criteriaQuery);List<Employee> result = testQuery.setParameter(age, 24).getResultList();Corresponding SQL: SELECT * FROM Employee WHERE age = 24
Ordering the Results
The results of the criteria query can be ordered by invoking the CriteriaQuery.orderBy
method, which takes an Order
object as the argument. By invoking CriteriaBuilder.asc
or CriteriaBuilder.Desc
, an Order
object can be created. In the following code snippet the Employee
instances are ordered based on the ascending order of the age
.
CriteriaQuery<Employee> criteriaQuery = criteriaBuilder .createQuery(Employee.class);Root<Employee> employee = criteriaQuery.from(Employee.class);criteriaQuery.orderBy(criteriaBuilder.asc(employee.get(Employee_.age)));em.createQuery(criteriaQuery).getResultList();Corresponding SQL: SELECT * FROM Employee ORDER BY age ASC
GroupBy
The groupBy
method of the CriteriaQuery instance is used to group results based on an Expression
. The queries are further filtered by calling the having
method by setting an additional expression. In the following code snippet the query is grouped by the name
property of the Employee
class, which starts with the letter N.
CriteriaQuery<Tuple> cq = criteriaBuilder.createQuery(Tuple.class);Root<Employee> employee = cq.from(Employee.class);cq.groupBy(employee.get(Employee_.name));cq.having(criteriaBuilder.like(employee.get(Employee_.name), "N%"));cq.select(criteriaBuilder.tuple(employee.get(Employee_.name),criteriaBuilder.count(employee)));TypedQuery<Tuple> q = em.createQuery(cq);List<Tuple> result = q.getResultList(); Corresponding SQL: SELECT name, COUNT(*) FROM employeeGROUP BY name HAVING name like 'N%'
Query Projection
The result of a criteria query is the same as what is specified during the criteria query creation. The result can also be explicitly specified by passing a query root to the CriteriaQuery.select
. The criteria query also gives the developer the strength to project the result in various types, as discussed in the following sections.
Using the construct()
Using this approach, the results of a query can be made to be of a non entity type. In the following code snippet, a criteria query object is created for the EmployeeDetails
class, which is a non entity type. This criteria query object is necessary for the EmployeeDetails
class to have a constructor that accepts a String
and an Integer
type argument. The CriteriaBuilder.construct
method is used to create the instance of the EmployeeDetails
type.
CriteriaQuery<EmployeeDetails> criteriaQuery = criteriaBuilder.createQuery(EmployeeDetails.class);Root<Employee> employee = criteriaQuery.from(Employee.class);criteriaQuery.select(criteriaBuilder.construct(EmployeeDetails.class, employee.get(Employee_.name), employee.get(Employee_.age)));em.createQuery(criteriaQuery).getResultList();Corresponding SQL: SELECT name, age FROM employee
Query Returning Object[]
Criteria queries can also return the Object[]
type by setting the values to the CriteriaBuilder.array
method. In the following code snippet, the array size is 2 (composed of a String
and an Integer
value).
CriteriaQuery<Object[]> criteriaQuery = criteriaBuilder.createQuery(Object[].class);Root<Employee> employee = criteriaQuery.from(Employee.class);criteriaQuery.select(criteriaBuilder.array(employee.get(Employee_.name), employee.get(Employee_.age)));em.createQuery(criteriaQuery).getResultList();Corresponding SQL: SELECT name, age FROM employee
Query Returning a Tuple
A row of data or a single record in the database is generally referred to as a tuple. Queries can be made on the tuple by invoking the CriteriaBuilder.createTupleQuery()
method. A CriteriaQuery.multiselect
method is passed with the parameters, which must be returned from the query in a single record of data.
CriteriaQuery<Tuple> criteriaQuery = criteriaBuilder.createTupleQuery();Root<Employee> employee = criteriaQuery.from(Employee.class);criteriaQuery.multiselect(employee.get(Employee_.name).alias("name"), employee.get(Employee_.age).alias("age"));em.createQuery(criteriaQuery).getResultList();Corresponding SQL: SELECT name, age FROM employee
Conclusion
Criteria queries are a means of querying the database in a more object-oriented fashion. In this article I discussed typesafe criteria queries in JPA 2 as well as the concept of metamodel, which is vital for understanding criteria queries. The various APIs for criteria queries were also discussed.
Acknowledgements
The author would like to sincerely thank Mr. Subrahmanya SV (VP, ECOM Research Group, E&R) for his constant encouragement and Ms. Sangeetha S for providing ideas, guidance and valuable comments, as well as kindly reviewing this article.
About the Author
Nitin KL works at the E-Commerce Research Labs at Infosys Technologies. He is involved in design and development of Java EE applications using Hibernate, iBATIS, and JPA.