Querying in JPA 2: Typesafe and Object Oriented

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 and age properties of Employee , a static property SingularAttribute<A, B> b is defined, where b is an object of type B defined in class A .

, and properties of , a static property is defined, where is an object of type defined in class . For a collection type such as addresses defined in com.entities.Employee , a static property of type ListAttribute<A, B> b is defined, where the List object b is of type B defined in class A . The other collection types can be of SetAttribute , MapAttribute or CollectionAttribute 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.

Page 1 of 3