http://www.developer.com/java/ent/article.php/3902911/Querying-in-JPA-2-Typesafe-and-Object-Oriented.htm
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. 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 Consider an The name of the canonical metamodel class for Here is the generated metamodel class using the annotation processor: 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 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: The compiler assumes that the property named 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 To better understand criteria queries, consider a The following code snippet shows a criteria query, which is used to obtain all the employee instances whose age is greater than 24. A A Filtering conditions are applied to the SQL statements in the The following code snippet shows a The An executable query instance is created when the A metamodel instance is obtained by invoking the It is also possible to obtain metamodel information by invoking the An Expression object is used in a An Expression object can be obtained using the 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 In SQL, joins are performed to browse through multiple tables and obtain query results. Similarly, entity joins are performed by invoking the The 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 The semantics of the Objects, such as In JPQL, query parameters were passed at run time by using the named parameter syntax (variable preceded by a colon, e.g. The results of the criteria query can be ordered by invoking the The 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 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 Criteria queries can also return the 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 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. 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. 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.
Querying in JPA 2: Typesafe and Object Oriented
September 9, 2010
The Metamodel Concept
EntityManager.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
}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: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.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.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;}Employee entity in this case) can be accessed at compile time. This feature makes criteria queries more typesafe at compile time itself.Class myClass = Class.forName("com.demo.Test");Field myField = myClass.getField("myName");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.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
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;}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 > 24Building a CriteriaQuery Instance
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.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
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.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);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
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();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_);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
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)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
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 = 25Join Queries
From.join method. Joins help in navigating from one entity to another to obtain criteria query results.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 = 1Fetch Joins
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.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.deptIdPath Expression
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
: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 = 24Ordering the Results
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 ASCGroupBy
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
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()
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 employeeQuery Returning Object[]
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 employeeQuery Returning a Tuple
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 employeeConclusion
Acknowledgements
About the Author