http://www.developer.com/

Back to article

Querying in JPA 2: Typesafe and Object Oriented


September 9, 2010

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.
  • 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.

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.

Sitemap | Contact Us

Thanks for your registration, follow us on our social networks to keep up-to-date