CHAPTER 9

image

Criteria API

In the last chapter, we looked in detail at the JP QL query language and the concepts that underlie the JPA query model. In this chapter, we will look at an alternate method for constructing queries that uses a Java programming language API instead of JP QL or native SQL.

We will begin with an overview of the JPA Criteria API and look at a common use case involving constructing dynamic queries in an enterprise application. This will be followed by an in-depth exploration of the Criteria API and how it relates to JP QL.

A related feature of the Criteria API is the JPA metamodel API. We will conclude this chapter with an overview of the metamodel API and look at how it can be used to create strongly typed queries using the Criteria API.

Note that this chapter assumes that you have read Chapter 8 and are familiar with all the concepts and terminology that it introduces. Wherever possible, we will use the upper-case JP QL keywords to highlight different elements of the JPA query model and demonstrate their equivalent behavior with the Criteria API.

Overview

Before languages like JP QL became standardized, the most common method for constructing queries in many persistence providers was through a programming API. The query framework in EclipseLink, for example, was the most effective way to truly unlock the full power of its query engine. And, even with the advent of JP QL, programming APIs have still remained in use to give access to features not yet supported by the standard query language.

JPA 2.0 introduced a Criteria API for constructing queries that standardizes many of the programming features that exist in proprietary persistence products. More than just a literal translation of JP QL to programming interface, it also adopts programming best practices of the proprietary models, such as method chaining, and makes full use of the Java programming language features.

The following sections provide a high-level view of the Criteria API, discussing how and when it is appropriate to use. We also look at a more significant example with a use case that is common in many enterprise settings.

The Criteria API

Let’s begin with a simple example to demonstrate the syntax and usage of the Criteria API. The following JP QL query returns all the employees in the company with the name of “John Smith:”

SELECT e
FROM Employee e
WHERE e.name = 'John Smith'

And here is the equivalent query constructed using the Criteria API:

CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Employee> c = cb.createQuery(Employee.class);
Root<Employee> emp = c.from(Employee.class);
c.select(emp)
 .where(cb.equal(emp.get("name"), "John Smith"));

There is a lot going on in just a few lines of code in this example, but right away you should see parallels between the JP QL version and the criteria-based version. The JP QL keywords SELECT, FROM, WHERE and LIKE have matching methods in the form of select(), from(), where(), and like(). The Employee entity class takes the place of the entity name in the invocation of from(), and the name attribute of Employee is still being accessed, but instead of the JP QL dot operator here we have the get() method.

As we progress through this chapter, we will explore each of these methods in detail, but for now we will look at the bigger picture. First, there's the CriteriaBuilder interface, obtained here from the EntityManager interface through the getCriteriaBuilder() method. The CriteriaBuilder interface is the main gateway into the Criteria API, acting as a factory for the various objects that link together to form a query definition. The variable cb will be used in the examples in this chapter to represent the CriteriaBuilder object.

The first use of the CriteriaBuilder interface in this example is to create an instance of CriteriaQuery. The CriteriaQuery object forms the shell of the query definition and generally contains the methods that match up with the JP QL query clauses. The second use of the CriteriaBuilder interface in this example is to construct the conditional expressions in the WHERE clause. All of the conditional expression keywords, operators, and functions from JP QL are represented in some manner on the CriteriaBuilder interface.

Given that background, it is easy to see how the query comes together. The first step is to establish the root of the query by invoking from() to get back a Root object. This is equivalent to declaring the identification variable e in the JP QL example and the Root object will form the basis for path expressions in the rest of the query. The next step establishes the SELECT clause of the query by passing the root into the select() method. The last step is to construct the WHERE clause, by passing an expression composed from CriteriaBuilder methods that represent JP QL condition expressions into the where() method. When path expressions are needed, such as accessing the name attribute in this example, the get() method on the Root object is used to create the path.

Parameterized Types

The preceding example demonstrated the use of Java generics in the Criteria API. The API uses parameterized types extensively: almost every interface and method declaration uses Java generics in one form or another. Generics allow the compiler to detect many cases of incompatible type usage and, like the Java collection API, removes the need for casting in most cases.

Any API that uses Java generics can also be used without type parameters, but when compiled the code will emit compiler warnings. For example, code that uses a simple untyped (“raw”) List type will generate a warning to the effect that a reference to a generic type should be parameterized. The following line of code will generate two such warnings, one for using List and one for using ArrayList:

List list = new ArrayList();

The Criteria API can similarly be used without binding the criteria objects to specific types, although this clearly discards the typing benefits. The preceding example could be rewritten as:

CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery c = cb.createQuery(Employee.class);
Root emp = c.from(Employee.class);
c.select(emp)
 .where(cb.equal(emp.get(“name”), "John Smith"));

This code is functionally identical to the original example, but just happens to be more prone to errors during development. Nevertheless, some people may be willing to have less development-time type safety but more code readability in the absence of “type clutter.” This is particularly understandable considering that most people run at least minimal tests on a query before shipping the query code, and once you get to the point of knowing that the query works, then you are already as far ahead as you would be with compile-time type checking.

If you are in the category of people who would rather the code were simpler to read and develop at the cost of somewhat less compile-time safety, then depending upon your tolerance for compiler warnings you may want to disable them. This can be achieved by adding a @SuppressWarnings("unchecked") annotation on your class. However, be warned (no pun intended!) that this will cause all type checking warnings to be suppressed, not just the ones relating to the use of the Criteria API.

Dynamic Queries

To demonstrate a good potential use of the Criteria API, we will look at a common use case in many enterprise applications: crafting dynamic queries where the structure of the criteria is not known until runtime.

In Chapter 7, we discussed how to create dynamic JP QL queries. You build up the query string at runtime and then pass it to the createQuery() method of the EntityManager interface. The query engine parses the query string and returns a Query object that you can use to get results. Creating dynamic queries is required in situations where the output or criteria of a query varies depending on end-user choices.

Consider a web application used to search for employee contact information. This is a common feature in many large organizations that allows users to search by name, department, phone number, or even location, either separately or using a combination of query terms. Listing 9-1 shows an example implementation of a session bean that accepts a set of criteria and then builds up and executes a JP QL query depending on which criteria parameters have been set. It does not use the Criteria API. This example and others in this chapter use the data model described in Figure 8-1 in Chapter 8.

Listing 9-1.  Employee Search Using Dynamic JP QL Query

@Stateless
public class SearchService {
    @PersistenceContext(unitName="EmployeeHR")
    EntityManager em;

    public List<Employee> findEmployees(String name, String deptName,
                                  String projectName, String city) {
        StringBuffer query = new StringBuffer();
        query.append("SELECT DISTINCT e ");
        query.append("FROM Employee e LEFT JOIN e.projects p ");

        query.append("WHERE ");
        List<String> criteria = new ArrayList<String>();
        if (name != null) { criteria.add("e.name = :name"); }
        if (deptName != null) { criteria.add("e.dept.name = :dept"); }
        if (projectName != null) { criteria.add("p.name = :project"); }
        if (city != null) { criteria.add("e.address.city = :city"); }
        if (criteria.size() == 0) {
            throw new RuntimeException("no criteria");
        }
        for (int i = 0; i < criteria.size(); i++) {
            if (i > 0) { query.append(" AND "); }
            query.append(criteria.get(i));
        }

        Query q = em.createQuery(query.toString());
        if (name != null) { q.setParameter("name", name); }
        if (deptName != null) { q.setParameter("dept", deptName); }
        if (projectName != null) { q.setParameter("project", projectName); }
        if (city != null) { q.setParameter("city", city); }
        return(List<Employee>)q.getResultList();
    }
}

The findEmployees() method in Listing 9-1 has to perform a number of tasks every time it is invoked. It has to build up a query string with a variable set of criteria, create the query, bind parameters, and then execute the query. It’s a fairly straightforward implementation, and will do the job, but every time the query string is created the provider has to parse the JP QL and build up an internal representation of the query before parameters can be bound and SQL generated. It would be nice if we could avoid the parsing overhead and construct the various criteria options using Java API instead of strings. Consider Listing 9-2.

Listing 9-2.  Employee Search Using Criteria API

@Stateless
public class SearchService {
    @PersistenceContext(unitName="EmployeeHR")
    EntityManager em;

    public List<Employee> findEmployees(String name, String deptName,
                                  String projectName, String city) {

        CriteriaBuilder cb = em.getCriteriaBuilder();
        CriteriaQuery<Employee> c = cb.createQuery(Employee.class);
        Root<Employee> emp = c.from(Employee.class);
        c.select(emp);
        c.distinct(true);
        Join<Employee,Project> project =
            emp.join("projects", JoinType.LEFT);

        List<Predicate> criteria = new ArrayList<Predicate>();
        if (name != null) {
            ParameterExpression<String> p =
                cb.parameter(String.class, "name");
            criteria.add(cb.equal(emp.get("name"), p));
        }
        if (deptName != null) {
            ParameterExpression<String> p =
                cb.parameter(String.class, "dept");
            criteria.add(cb.equal(emp.get("dept").get("name"), p));
        }
        if (projectName != null) {
            ParameterExpression<String> p =
                cb.parameter(String.class, "project");
            criteria.add(cb.equal(project.get("name"), p));
        }
        if (city != null) {
            ParameterExpression<String> p =
                cb.parameter(String.class, "city");
            criteria.add(cb.equal(emp.get("address").get("city"), p));
        }

        if (criteria.size() == 0) {
            throw new RuntimeException("no criteria");
        } else if (criteria.size() == 1) {
           c.where(criteria.get(0));
        } else {
            c.where(cb.and(criteria.toArray(new Predicate[0])));
        }
        
        TypedQuery<Employee> q = em.createQuery(c);
        if (name != null) { q.setParameter("name", name); }
        if (deptName != null) { q.setParameter("dept", deptName); }
        if (project != null) { q.setParameter("project", projectName); }
        if (city != null) { q.setParameter("city", city); }
        return q.getResultList();
    }
}

Listing 9-2 shows the same EmployeeSearch service from Listing 9-1 redone using the Criteria API. This is a much larger example than our initial look at the Criteria API, but once again you can see the general pattern of how it is constructed. The basic query construction and clause methods of the CriteriaBuilder and CriteriaQuery interfaces are present as before, but there are a few new elements in this query we can explore. The first is the join between Employee and Project, here constructed using the join() method on the Root object. The Join object that is returned can also be used to construct path expressions such as the Root object. In this example, we can also see a path expression involving more than one relationship, from Employee to Address to the city attribute.

The second new element in this query is the use of parameters. Unlike JP QL where parameters are just an alias, in the Criteria API parameters are strongly typed and created from the parameter() call. The ParameterExpression object that is returned can then be used in other parts of the query such as the WHERE or SELECT clauses. In terms of expressions, this example also includes the CriteriaBuilder methods equal() and and(), equivalent to the JP QL predicates = and AND. Note the somewhat odd invocation of the and() method. Like many other Criteria API methods, and() accepts a varying number of arguments, which in turn can be represented as an array of the appropriate argument type. Unfortunately, the designers of the Collection.toArray() method decided that, in order to avoid casting the return type, an array to be populated should also be passed in as an argument or an empty array in the case where we want the collection to create the array for us. The syntax in the example is shorthand for the following code:

Predicate[] p = new Predicate[criteria.size()];
p = criteria.toArray(p);
c.where(cb.and(p));

The last feature in this query that we did not demonstrate previously is the execution of the query itself. As we demonstrated in Chapter 7, the TypedQuery interface is used to obtain strongly typed query results. Query definitions created with the Criteria API have their result type bound using Java generics and therefore always yield a TypedQuery object from the createQuery() method of the EntityManager interface.

Building Criteria API Queries

Our high-level look at Criteria API examples concluded, the following sections will look at each aspect of creating a query definition in detail. Wherever possible, we try to highlight the similarity between JP QL and Criteria API concepts and terminology.

Creating a Query Definition

As we demonstrated in the previous sections, the heart of the Criteria API is the CriteriaBuilder interface, obtained from the EntityManager interface by calling the getCriteriaBuilder() method. The CriteriaBuilder interface is large and serves several purposes within the Criteria API. It is a factory with which we create the query definition itself, an instance of the CriteriaQuery interface, as well as many of various components of the query definition such as conditional expressions.

The CriteriaBuilder interface provides three methods for creating a new select query definition, depending on the desired result type of the query. The first and most common method is the createQuery(Class<T>) method, passing in the class corresponding to the result of the query. This is the approach we used in Listing 9-2. The second method is createQuery(), without any parameters, and corresponds to a query with a result type of Object. The third method, createTupleQuery(), is used for projection or report queries where the SELECT clause of the query contains more than one expression and you wish to work with the result in a more strongly typed manner. It is really just a convenience method that is equivalent to invoking createQuery(Tuple.class). Note that Tuple is an interface that contains an assortment of objects or data and applies typing to the aggregate parts. It can be used whenever multiple items are returned and you want to combine them into a single typed object.

It is worth noting that, despite the name, a CriteriaQuery instance is not a Query object that may be invoked to get results from the database. It is a query definition that may be passed to the createQuery() method of the EntityManager interface in place of a JP QL string. The only real difference between a criteria query definition and a JP QL string is the method of building the query definition (programming API versus text) and that criteria queries are typically typed, so the result type does not have to be specified when invoking createQuery() on EntityManager in order to obtain a TypedQuery instance. You may also find it useful to think of a fully defined CriteriaQuery instance as being similar to the internal representation of a JP QL query that a persistence provider might use after parsing the JP QL string.

The Criteria API is comprised of a number of interfaces that work together to model the structure of a JPA query. As you progress through this chapter, you may find it useful to refer to the interface relationships shown in Figure 9-1.

9781430249269_Fig09-01.jpg

Figure 9-1. Criteria API Interfaces

Basic Structure

In the discussion of JP QL in Chapter 8, you learned that there are six possible clauses to be used in a select query: SELECT, FROM, WHERE, ORDER BY, GROUP BY and HAVING. Each of these JP QL clauses has an equivalent method on one of the Criteria API query definition interfaces. Table 9-1 summarizes these methods.

Table 9-1. Criteria API Select Query Clause Methods

JP QL Clause Criteria API Interface Method
SELECT CriteriaQuery select()
Subquery select()
FROM AbstractQuery from()
WHERE AbstractQuery where()
ORDER BY CriteriaQuery orderBy()
GROUP BY AbstractQuery groupBy()
HAVING AbstractQuery having()

As we have demonstrated, there is a strong symmetry between the JP QL language and the Criteria API methods. Wherever possible, the same name has been used, making it easy to anticipate the name of a Criteria API method, even if you have not used it before. Over the next several sections, we look at each of these clause methods in detail and how expressions are formed using the Criteria API.

Criteria Objects and Mutability

Typical usage of the Criteria API will result in many different objects being created. In addition to the primary CriteriaBuilder and CriteriaQuery objects, every component of every expression is represented by one object or another. Not all objects are created equal, however, and effective use of the Criteria API requires familiarity with the coding patterns assumed in its design.

The first issue we need to consider is one of mutability. The majority of objects created through the Criteria API are in fact immutable. There are no setter methods or mutating methods on these interfaces. Almost all of the objects created from the methods on the CriteriaBuilder interface fall into this category.

The use of immutable objects means that the arguments passed into the CriteriaBuilder methods are rich in detail. All relevant information must be passed in so that the object can be complete at the time of its creation. The advantage of this approach is that it facilitates chained invocations of methods. Because no mutating methods have to be invoked on the objects returned from the methods used to build expressions, control can immediately continue to the next component in the expression.

Only the CriteriaBuilder methods that create query definition objects produce truly mutable results. The CriteriaQuery and Subquery objects are intended to be modified many times by invoking methods such as select(), from(), and where(). But even here care must be taken as invoking methods twice can have one of two different effects. In most cases, invoking a method twice replaces the contents within the object with the argument being supplied. For example, invoking select() twice with two different arguments results in only the argument from the second invocation actually remaining as part of the query definition.

In some cases, however, invoking a method twice is in fact addition. Invoking from() twice with different arguments results in multiple query roots being added to the query definition. While we refer to these cases in the sections where they are described, you should be familiar with the Javadoc comments on the Criteria API as they also call out this behavior.

The second issue is the presence of getter methods on Criteria API objects. These behave as expected, returning information about the component of the query that each object represents. But it is worth noting that such methods are primarily of interest to tool developers who wish to work with query definitions in a truly generic way. In the vast majority of cases, and those that we demonstrate in this chapter, you will not have to make use of the getter methods in the construction of your criteria queries.

Query Roots and Path Expressions

A newly created CriteriaQuery object is basically an empty shell. With the exception of defining the result type of the query, no additional content has yet been added to fill out the query. As with JP QL queries, the developer is responsible for defining the various clauses of the query necessary to fetch the desired data from the database. Semantically speaking, there is no difference between JP QL and Criteria API query definitions. Both have SELECT, FROM, WHERE, GROUP BY, HAVING and ORDER clauses; only the manner of defining them is different. Before we can fill in the various clauses of the query definition, let's first revisit two key concepts defined in Chapter 8 and look at the equivalent Criteria API syntax for those concepts.

Query Roots

The first fundamental concept to revisit is the identification variable used in the FROM clause of JP QL queries to alias declarations that cover entity, embeddable, and other abstract schema types. In JP QL, the identification variable takes on a central importance, as it is the key to tying the different clauses of the query together. But with the Criteria API we represent query components with objects and therefore rarely have aliases with which to concern ourselves. Still, in order to define a FROM clause we need a way to express which abstract schema types we are interested in querying against.

The AbstractQuery interface (parent of CriteriaQuery) provides the from() method to define the abstract schema type that will form the basis for the query. This method accepts an entity type as a parameter and adds a new root to the query. A root in a criteria query corresponds to an identification variable in JP QL, which in turn corresponds to a range variable declaration or join expression. In Listing 9-2, we used the following code to obtain our query root:

CriteriaQuery<Employee> c = cb.createQuery(Employee.class);
Root<Employee> emp = c.from(Employee.class);

The from() method returns an instance of Root corresponding to the entity type. The Root interface is itself extended from the From interface, which exposes functionality for joins. The From interface extends Path, which further extends Expression and then Selection, allowing the root to be used in other parts of the query definition. The role of each of these interfaces will be described in later sections. Calls to the from() method are additive. Each call adds another root to the query, resulting in a Cartesian product when more than one root is defined if no further constraints are applied in the WHERE clause. The following example from Chapter 8 demonstrates multiple query roots, replacing a conventional join with the more traditional SQL approach:

SELECT DISTINCT d
FROM Department d, Employee e
WHERE d = e.department

To convert this query to the Criteria API we need to invoke from() twice, adding both the Department and Employee entities as query roots. The following example demonstrates this approach:

CriteriaQuery<Department> c = cb.createQuery(Department.class);
Root<Department> dept = c.from(Department.class);
Root<Employee> emp = c.from(Employee.class);
c.select(dept)
 .distinct(true)
 .where(cb.equal(dept, emp.get("department")));

Path Expressions

The second fundamental concept to revisit is the path expression. The path expression is the key to the power and flexibility of the JP QL language, and it is likewise a central piece of the Criteria API. We discussed path expressions in detail in Chapter 8 so if you feel you need a refresher we recommend going back to review that section.

We went over query roots in the previous section, and roots are actually just a special type of path expression. Query roots in hand, we can now look at how to obtain and extend path expressions. Consider the following basic JP QL query, which returns all the employees living in New York City:

SELECT e
FROM Employee e
WHERE e.address.city = 'New York'

Thinking in terms of the Criteria API, the query root for this expression is the Employee entity. This query also contains a path expression in the WHERE clause. To represent this path expression using the Criteria API, we would use the following expression:

emp.get("address").get("city")

The emp object in this example corresponds to the query root for Employee. The get() method is derived from the Path interface extended by the Root interface and is equivalent to the dot operator used in JP QL path expressions to navigate along a path. Because the get() method returns a Path object, the method calls can be chained together, avoiding the unnecessary declaration of intermediate local variables. The argument to get() is the name of the attribute we are interested in. Because the result of constructing a path expression is an Expression object that we can use to build conditional expressions, we can then express the complete query as follows:

CriteriaQuery<Employee> c = cb.createQuery(Employee.class);
Root<Employee> emp = c.from(Employee.class);
c.select(emp)
 .where(cb.equal(emp.get("address").get("city"), "New York"));

Much like JP QL, path expressions may be used throughout the different clauses of the query definition. With the Criteria API it is necessary to hold onto the root object in a local variable and use it to form path expressions where required. Once again it is worth emphasizing that the from() method of AbstractQuery should never be invoked more than once for each desired root. Invoking it multiple times will result in additional roots being created and a Cartesian product if not careful. Always store the root objects locally and refer to them when necessary.

The SELECT Clause

There are several forms that the SELECT clause of a query may take. The simplest form involves a single expression, while others involve multiple expressions or the use of a constructor expression to create new object instances. Each form is expressed differently in the Criteria API.

Selecting Single Expressions

The select() method of the CriteriaQuery interface is used to form the SELECT clause in a Criteria API query definition. All forms of the SELECT clause may be represented via the select() method, although convenience methods also exist to simplify coding. The select() method requires an argument of type Selection, an interface extended by Expression as well as CompoundSelection to handle the case where the result type of a query is a Tuple or array of results.

image Note  Some vendors may allow the call to select() to be omitted in the case where there is a single query root and it matches the declared result type for the query. This is non-portable behavior.

Thus far, we have been passing in a query root to the select() method, therefore indicating that we want the entity to be the result of the query. We could also supply a single-valued expression such as selecting an attribute from an entity or any compatible scalar expression. The following example demonstrates this approach by selecting the name attribute of the Employee entity:

CriteriaQuery<String> c = cb.createQuery(String.class);
Root<Employee> emp = c.from(Employee.class);
c.select(emp.<String>get("name"));

This query will return all employee names, including any duplicates. Duplicate results from a query may be removed by invoking distinct(true) from the AbstractQuery interface. This is identical in behavior to the DISTINCT keyword in a JP QL query.

Also note the unusual syntax we used to declare that the “name” attribute was of type String. The type of the expression provided to the select() method must be compatible with the result type used to create the CriteriaQuery object. For example, if the CriteriaQuery object was created by invoking createQuery(Project.class) on the CriteriaBuilder interface, then it will be an error to attempt to set an expression resolving to the Employee entity using the select() method. When a method call such as select() uses generic typing in order to enforce a compatibility constraint, the type may be prefixed to the method name in order to qualify it in cases where the type could not otherwise be automatically determined. We need to use that approach in this case because the select() method has been declared as follows:

CriteriaQuery<T> select(Selection<? extends T> selection);

The argument to select() must be a type that is compatible with the result type of the query definition. The get() method returns a Path object, but that Path object is always of type Path<Object> because the compiler cannot infer the correct type based on the attribute name. To declare that the attribute is really of type String, we need to qualify the method invocation accordingly. This syntax has to be used whenever the Path is being passed as an argument for which the parameter has been strongly typed, such as the argument to the select() method and certain CriteriaBuilder expression methods. We have not had to use them so far in our examples because we have been using them in methods like equal(), where the parameter was declared to be of type Expression<?>. Because the type is wildcarded, it is valid to pass in an argument of type Path<Object>. Later in the chapter, we will look at the strongly typed versions of the Criteria API methods that remove this requirement.

Selecting Multiple Expressions

When defining a SELECT clause that involves more than one expression, the Criteria API approach required depends on how the query definition was created. If the result type is Tuple, then a CompoundSelection<Tuple> object must be passed to select(). If the result type is a non-persistent class that will be created using a constructor expression, then the argument must be a CompoundSelection<[T]> object, where [T] is the class type of the non-persistent class. Finally, if the result type is an array of objects, then a CompoundSelection<Object[]> object must be provided. These objects are created with the tuple(), construct() and array() methods of the CriteriaBuilder interface, respectively. The following example demonstrates how to provide multiple expressions to a Tuple query:

CriteriaQuery<Tuple> c= cb.createTupleQuery();
Root<Employee> emp = c.from(Employee.class);
c.select(cb.tuple(emp.get("id"), emp.get("name")));

As a convenience, the multiselect() method of the CriteriaQuery interface may also be used to set the SELECT clause. The multiselect() method will create the appropriate argument type given the result type of the query. This can take three forms depending on how the query definition was created.

The first form is for queries that have Object or Object[] as their result type. The list of expressions that make up each result are simply passed to the multiselect() method.

CriteriaQuery<Object[]> c = cb.createQuery(Object[].class);
Root<Employee> emp = c.from(Employee.class);
c.multiselect(emp.get("id"), emp.get("name"));

Note that, if the query result type is declared as Object instead of Object[], the behavior of multiselect() in this form changes slightly. The result is always an instance of Object, but if multiple arguments are passed into multiselect() then the result must be cast to Object[] in order to access any particular value. If only a single argument is passed into multiselect(), then no array is created and the result may be cast directly from Object to the desired type. In general, it is more convenient to be explicit about the query result type. If you want to work with an array of results, then declaring the query result type to be Object[] avoids casting later and makes the shape of the result more explicit if the query is invoked separately from the code that creates it.

The second form is a close relative of the first form, but for queries that result in Tuple. Again, the list of expressions is passed into the multiselect() call.

CriteriaQuery<Tuple> c = cb.createTupleQuery();
Root<Employee> emp = c.from(Employee.class);
c.multiselect(emp.get("id"), emp.get("name"));

The third and final form is for queries with constructor expressions that result in non-persistent types. The multiselect() method is again invoked with a list of expressions, but it uses the type of the query to figure out and automatically create the appropriate constructor expression, in this case a data transfer object of type EmployeeInfo.

CriteriaQuery<EmployeeInfo> c = cb.createQuery(EmployeeInfo.class);
Root<Employee> emp = c.from(Employee.class);
c.multiselect(emp.get("id"), emp.get("name"));

This is equivalent to the following:

CriteriaQuery<EmployeeInfo> c = cb.createQuery(EmployeeInfo.class);
Root<Employee> emp = c.from(Employee.class);
c.select(cb.construct(EmployeeInfo.class,
                      emp.get("id"),
                      emp.get("name")));

As convenient as the multiselect() method is for constructor expressions, there are still cases where you will need to use the construct() method from the CriteriaBuilder interface. For example, if the result type of the query is Object[] and it also includes a constructor expression for only part of the results, the following would be required:

CriteriaQuery<Object[]> c = cb.createQuery(Object[].class);
Root<Employee> emp = c.from(Employee.class);
c.multiselect(emp.get("id"),
              cb.construct(EmployeeInfo.class,
                           emp.get("id"),
                           emp.get("name")));

Using Aliases

Like JP QL, aliases may also be set on expressions in the SELECT clause, which will then be included in the resulting SQL statement. They are of little use from a programming perspective as we construct the ORDER BY clause through the use of the Selection objects used to construct the SELECT clause.

Aliases are useful when the query has a result type of Tuple. The aliases will be available through the resulting Tuple objects. To set an alias, the alias() method of the Selection interface (parent to Expression) must be invoked. The following example demonstrates this approach:

CriteriaQuery<Tuple> c= cb.createTupleQuery();
Root<Employee> emp = c.from(Employee.class);
c.multiselect(emp.get("id").alias("id"), emp.get("name").alias("fullName"));

This example actually demonstrates two facets of the alias() method. The first is that it returns itself, so it can be invoked as part of the call to select() or multiselect(). The second is, once again, that it returns itself, and is therefore mutating what should be an otherwise immutable object. The alias() method is an exception to the rule that only the query definition interfaces, CriteriaQuery and Subquery, contain mutating operations. Invoking alias() changes the original Selection object and returns it from the method invocation. It is invalid to set the alias of a Selection object more than once.

Making use of the alias when iterating over the query results is as simple as requesting the expression by name. Executing the previous query would allow it to be processed as follows:

TypedQuery<Tuple> q = em.createQuery(c);
for (Tuple t : q.getResultList()) {
    String id = t.get("id", String.class);
    String fullName = t.get("fullName", String.class);
    // ...
}

The FROM Clause

In the “Query Roots” section, we covered the from() method of the AbstractQuery interface and the role of query roots in forming the query definition. We will now elaborate on that discussion and look at how joins are expressed using the Criteria API.

Inner and Outer Joins

Join expressions are created using the join() method of the From interface, which is extended both by Root, which we covered earlier, and Join, which is the object type returned by creating join expressions. This means that any query root may join, and that joins may chain with one another. The join() method requires a path expression argument and optionally an argument to specify the type of join, JoinType.INNER or JoinType.LEFT, for inner and outer joins respectively.

image Tip  The JoinType.RIGHT enumerated value specifies that a right outer join should be applied. Support for this option is not required by the specification so applications that make use of it will not be portable.

When joining across a collection type (except for Map, which we will discuss later in this chapter), the join will have two parameterized types: the type of the source and the type of the target. This maintains the type safety on both sides of the join, and makes it clear what types are being joined.

The join() method is additive, so each call results in a new join being created; therefore, the Join instance returned from invoking the method should be retained in a local variable for forming path expressions later. Because Join also extends Path, it behaves like Root objects when defining paths.

In Listing 9-2, we demonstrated an outer join from Employee to Project.

Join<Employee,Project> project = emp.join("projects", JoinType.LEFT);

Had the JoinType.LEFT argument been omitted, the join type would have defaulted to be an inner join. Just as in JP QL, multiple joins may be associated with the same From instance. For example, to navigate across the directs relationship of Employee and then to both the Department and Project entities would require the following, which assumes inner joining:

Join<Employee,Employee> directs = emp.join("directs");
Join<Employee,Project> projects = directs.join("projects");
Join<Employee,Department> dept = directs.join("dept");

Joins may also be cascaded in a single statement. The resulting join will be typed by the source and target of the last join in the statement:

Join<Employee,Project> project = dept.join("employees").join("projects");

Joins across collection relationships that use Map are a special case. JP QL uses the KEY and VALUE keywords to extract the key or value of a Map element for use in other parts of the query. In the Criteria API, these operators are handled by the key() and value() methods of the MapJoin interface. Consider the following example assuming a Map join across the phones relationship of the Employee entity:

SELECT e.name, KEY(p), VALUE(p)
FROM Employee e JOIN e.phones p

To create this query using the Criteria API, we need to capture the result of the join as a MapJoin, in this case using the joinMap() method. The MapJoin object has three type parameters: the source type, key type, and value type. It can look a little more daunting, but makes it explicit what types are involved.

CriteriaQuery<Object> c = cb.createQuery();
Root<Employee> emp = c.from(Employee.class);
MapJoin<Employee,String,Phone> phone = emp.joinMap("phones");
c.multiselect(emp.get("name"), phone.key(), phone.value());

We need to use the joinMap() method in this case because there is no way to overload the join() method to return a Join object or MapJoin object when all we are passing in is the name of the attribute. Collection, Set, and List relationships are likewise handled with the joinCollection(), joinSet(), and joinList() methods for those cases where a specific join interface must be used. The strongly typed version of the join() method, which we will demonstrate later, is able to handle all join types though the single join() call.

Fetch Joins

As with JP QL, the Criteria API supports the fetch join, a query construct that allows data to be prefetched into the persistence context as a side effect of a query that returns a different, but related, entity. The Criteria API builds fetch joins through the use of the fetch() method on the FetchParent interface. It is used instead of join() in cases where fetch semantics are required and accepts the same argument types.

Consider the following example we used in the previous chapter to demonstrate fetch joins of single-valued relationships:

SELECT e
FROM Employee e JOIN FETCH e.address

To re-create this query with the Criteria API, we use the fetch() method.

CriteriaQuery<Employee> c = cb.createQuery(Employee.class);
Root<Employee> emp = c.from(Employee.class);
emp.fetch("address");
c.select(emp);

Note that when using the fetch() method the return type is Fetch, not Join. Fetch objects are not paths and may not be extended or referenced anywhere else in the query.

Collection-valued fetch joins are also supported and use similar syntax. In the following example, we demonstrate how to fetch the Phone entities associated with each Employee, using an outer join to prevent Employee entities from being skipped if they don’t have any associated Phone entities. We use the distinct() setting to remove any duplicates.

CriteriaQuery<Employee> c = cb.createQuery(Employee.class);
Root<Employee> emp = c.from(Employee.class);
emp.fetch("phones", JoinType.LEFT);
c.select(emp)
 .distinct(true);

The WHERE Clause

As you saw in Table 9-1 and in several examples, the WHERE clause of a query in the Criteria API is set through the where() method of the AbstractQuery interface. The where() method accepts either zero or more Predicate objects, or a single Expression<Boolean> argument. Each call to where() will render any previously set WHERE expressions to be discarded and replaced with the newly passed-in ones.

Building Expressions

The key to building up expressions with the Criteria API is the CriteriaBuilder interface. This interface contains methods for all of the predicates, expressions, and functions supported by the JP QL language as well as other features specific to the Criteria API. Table 9-2, Table 9-3, Table 9-4, and Table 9-5 summarize the mapping between JP QL operators, expressions, and functions to their equivalent methods on the CriteriaBuilder interface. Note that in some cases there is no direct equal to a method and a combination of CriteriaBuilder methods is required to get the same result. In other cases, the equivalent criteria method is actually on a class other than CriteriaBuilder.

Table 9-2. JP QL to CriteriaBuilder Predicate Mapping

JP QL Operator CriteriaBuilder Method
AND and()
OR or()
NOT not()
= equal()
<> notEqual()
> greaterThan(), gt()
>= greaterThanOrEqualTo(), ge()
< lessThan(), lt()
<= lessThanOrEqualTo(), le()
BETWEEN between()
IS NULL isNull()
IS NOT NULL isNotNull()
EXISTS exists()
NOT EXISTS not(exists())
IS EMPTY isEmpty()
IS NOT EMPTY isNotEmpty()
MEMBER OF isMember()
NOT MEMBER OF isNotMember()
LIKE like()
NOT LIKE notLike()
IN in()
NOT IN not(in())

Table 9-3. JP QL to CriteriaBuilder Scalar Expression Mapping

JP QL Expression CriteriaBuilder Method
ALL all()
ANY any()
SOME some()
- neg(), diff()
+ sum()
* prod()
/ quot()
COALESCE coalesce()
NULLIF nullif()
CASE selectCase()

Table 9-4. JP QL to CriteriaBuilder Function Mapping

JP QL Function CriteriaBuilder Method
ABS abs()
CONCAT concat()
CURRENT_DATE currentDate()
CURRENT_TIME currentTime()
CURRENT_TIMESTAMP currentTimestamp()
LENGTH length()
LOCATE locate()
LOWER lower()
MOD mod()
SIZE size()
SQRT sqrt()
SUBSTRING substring()
UPPER upper()
TRIM trim()

Table 9-5. JP QL to CriteriaBuilder Aggregate Function Mapping

JP QL Aggregate Function CriteriaBuilder Method
AVG avg()
SUM sum(), sumAsLong(), sumAsDouble()
MIN min(), least()
MAX max(), greatest()
COUNT count()
COUNT DISTINCT countDistinct()

In addition to the straight translation of JP QL operators, expressions, and functions, there are some techniques specific to the Criteria API that need to be considered when developing expressions. The following sections will look at these techniques in detail and explore those parts of the Criteria API that have no equivalent in JP QL.

Predicates

In Listing 9-2, we passed an array of Predicate objects to the and() method. This has the behavior of combining all of the expressions with the AND operator. Equivalent behavior for the OR operator exists via the or() method. One shortcut that works for AND operators is to pass all of the expressions as arguments to the where() method. Passing multiple arguments to where() implicitly combines the expressions using AND operator semantics.

The Criteria API also offers a different style of building AND and OR expressions for those who wish to build things incrementally rather than as a list. The conjunction() and disjunction() methods of the CriteriaBuilder interface create Predicate objects that always resolve to true and false respectively. Once obtained, these primitive predicates can then be combined with other predicates to build up nested conditional expressions in a tree-like fashion. Listing 9-3 rewrites the predication construction portion of the example from Listing 9-2 using the conjunction() method. Note how each conditional statement is combined with its predecessor using an and() call.

Listing 9-3.  Predicate Construction Using Conjunction

Predicate criteria = cb.conjunction();
if (name != null) {
    ParameterExpression<String> p =
        cb.parameter(String.class, "name");
    criteria = cb.and(criteria, cb.equal(emp.get("name"), p));
}
if (deptName != null) {
    ParameterExpression<String> p =
        cb.parameter(String.class, "dept");
    criteria = cb.and(criteria,
                      cb.equal(emp.get("dept").get("name"), p));
}
if (projectName != null) {
    ParameterExpression<String> p =
        cb.parameter(String.class, "project");
    criteria = cb.and(criteria, cb.equal(project.get("name"), p));
}
if (city != null) {
    ParameterExpression<String> p =
        cb.parameter(String.class, "city");
    criteria = cb.and(criteria,
                      cb.equal(emp.get("address").get("city"), p));
}

if (criteria.getExpressions().size() == 0) {
    throw new RuntimeException("no criteria");
}

With respect to other predicate concerns, in Table 9-2 it should be noted that there are two sets of methods available for relative comparisons. For example, there is greaterThan() and gt(). The two-letter forms are specific to numeric values and are strongly typed to work with number types. The long forms must be used for all other cases.

Literals

Literal values may require special handling when expressed with the Criteria API. In all the cases encountered so far, methods are overloaded to work with both Expression objects and Java literals. However, there may be some cases where only an Expression object is accepted (in cases where it is assumed you would never pass in a literal value or when any of a number of types would be acceptable). If you encounter this situation then, to use these expressions with Java literals, the literals must be wrapped using the literal() method. NULL literals are created from the nullLiteral() method, which accepts a class parameter and produces a typed version of NULL to match the passed-in class. This is necessary to extend the strong typing of the API to NULL values.

Parameters

Parameter handling for Criteria API queries is different from JP QL. Whereas JP QL strings simply prefix string names with a colon to denote a parameter alias, this technique will not work in the Criteria API. Instead, we must explicitly create a ParameterExpression of the correct type that can be used in conditional expressions. This is achieved through the parameter() method of the CriteriaBuilder interface. This method requires a class type (to set the type of the ParameterExpression object) and an optional name for use with named parameters. Listing 9-4 demonstrates this method.

Listing 9-4.  Creating Parameter Expressions

CriteriaQuery<Employee> c = cb.createQuery(Employee.class);
Root<Employee> emp = c.from(Employee.class);
c.select(emp);
ParameterExpression<String> deptName =
    cb.parameter(String.class, "deptName");
c.where(cb.equal(emp.get("dept").get("name"), deptName));

If the parameter will not be reused in other parts of the query, it can be embedded directly in the predicate expression to make the overall query definition more concise. The following code revises the Listing 9-4 to use this technique:

CriteriaQuery<Employee> c = cb.createQuery(Employee.class);
Root<Employee> emp = c.from(Employee.class);
c.select(emp)
 .where(cb.equal(emp.get("dept").get("name"),
                 cb.parameter(String.class, "deptName")));

Subqueries

The AbstractQuery interface provides the subquery() method for creation of subqueries. Subqueries may be correlated (meaning that they reference a root, path, or join from the parent query) or non-correlated. The Criteria API supports both correlated and non-correlated subqueries, again using query roots to tie the various clauses and expressions together. The argument to subquery() is a class instance representing the result type of the subquery. The return value is an instance of Subquery, which is itself an extension of AbstractQuery. With the exception of restricted methods for constructing clauses, the Subquery instance is a complete query definition like CriteriaQuery that may be used to create both simple and complex queries.

To demonstrate subquery usage, let’s look at a more significant example, modifying Listing 9-2 to use subqueries instead of the distinct() method to eliminate duplicates. According to the data model shown in Figure 8-1, the Employee entity has relationships with four other entities: single-valued relationships with Department and Address, and collection-valued relationships with Phone and Project. Whenever we join across a collection-valued relationship, we have the potential to return duplicate rows; therefore, we need to change the criteria expression for Project to use a subquery. Listing 9-5 shows the code fragment required to make this change.

Listing 9-5.  Modified Employee Search With Subqueries

@Stateless
public class SearchService {
    @PersistenceContext(unitName="EmployeeHR")
    EntityManager em;

    public List<Employee> findEmployees(String name, String deptName,
                                        String projectName, String city) {
        CriteriaBuilder cb = em.getCriteriaBuilder();
        CriteriaQuery<Employee> c = cb.createQuery(Employee.class);
        Root<Employee> emp = c.from(Employee.class);
        c.select(emp);

        // ...

        if (projectName != null) {
            Subquery<Employee> sq = c.subquery(Employee.class);
            Root<Project> project = sq.from(Project.class);
            Join<Project,Employee> sqEmp = project.join("employees");
            sq.select(sqEmp)
              .where(cb.equal(project.get("name"),
                              cb.parameter(String.class, "project")));
            criteria.add(cb.in(emp).value(sq));
        }

        // ...
}

Listing 9-5 contains a couple of significant changes to the example first presented in Listing 9-2. First, the distinct() method call has been removed as well as the join to the Project entity. We have also introduced a new non-correlated subquery against Project. Because the subquery from Listing 9-5 declares its own root and does not reference anything from the parent query, it runs independently and is therefore non-correlated. The equivalent JP QL query with only Project criteria would be:

SELECT e
FROM Employee e
WHERE e IN (SELECT emp
              FROM Project p JOIN p.employees emp
              WHERE p.name = :project)

Whenever we write queries that use subqueries, there is often more than one way to achieve a particular result. For example, we could rewrite the previous example to use EXISTS instead of IN and shift the conditional expression into the WHERE clause of the subquery.

if (projectName != null) {
    Subquery<Project> sq = c.subquery(Project.class);
    Root<Project> project = sq.from(Project.class);
    Join<Project,Employee> sqEmp = project.join("employees");
    sq.select(project)
      .where(cb.equal(sqEmp, emp),
             cb.equal(project.get("name"),
                      cb.parameter(String.class,"project")));
    criteria.add(cb.exists(sq));
}

By referencing the Employee root from the parent query in the WHERE clause of the subquery, we now have a correlated subquery. This time the query takes the following form in JP QL:

SELECT e
FROM Employee e
WHERE EXISTS (SELECT p
              FROM Project p JOIN p.employees emp
              WHERE emp = e AND
                    p.name = :name)

We can still take this example further and reduce the search space for the subquery by moving the reference to the Employee root to the FROM clause of the subquery and joining directly to the list of projects specific to that employee. In JP QL, we would write this as follows:

SELECT e
FROM Employee e
WHERE EXISTS (SELECT p
              FROM e.projects p
              WHERE p.name = :name)

In order to re-create this query using the Criteria API, we are confronted with a dilemma. We need to base the query on the Root object from the parent query but the from() method only accepts a persistent class type. The solution is the correlate() method from the Subquery interface. It performs a similar function to the from() method of the AbstractQuery interface, but does so with Root and Join objects from the parent query. The following example demonstrates how to use correlate() in this case:

if (projectName != null) {
    Subquery<Project> sq = c.subquery(Project.class);
    Root<Employee> sqEmp = sq.correlate(emp);
    Join<Employee,Project> project = sqEmp.join("projects");
    sq.select(project)
      .where(cb.equal(project.get("name"),
                      cb.parameter(String.class,"project")));
    criteria.add(cb.exists(sq));
}

Before we leave subqueries in the Criteria API, there is one more corner case with correlated subqueries to explore: referencing a join expression from the parent query in the FROM clause of a subquery. Consider the following example that returns projects containing managers with direct reports earning an average salary higher than a user-defined threshold:

SELECT p
FROM Project p JOIN p.employees e
WHERE TYPE(p) = DesignProject AND
      e.directs IS NOT EMPTY AND
      (SELECT AVG(d.salary)
       FROM e.directs d) >= :value

When creating the Criteria API query definition for this query, we must correlate the employees attribute of Project and then join it to the direct reports in order to calculate the average salary. This example also demonstrates the use of the type() method of the Path interface in order to do a polymorphic comparison of types:

CriteriaQuery<Project> c = cb.createQuery(Project.class);
Root<Project> project = c.from(Project.class);
Join<Project,Employee> emp = project.join("employees");
Subquery<Number> sq = c.subquery(Number.class);
Join<Project,Employee> sqEmp = sq.correlate(emp);
Join<Employee,Employee> directs = sqEmp.join("directs");
c.select(project)
 .where(cb.equal(project.type(), DesignProject.class),
        cb.isNotEmpty(emp.<Collection>get("directs")),
        cb.ge(sq.select(cb.avg(directs.get("salary"))),
                  cb.parameter(Number.class, "value")));

In Expressions

Unlike other operators, the IN operator requires some special handling in the Criteria API. The in() method of the CriteriaBuilder interface only accepts a single argument, the single-valued expression that will be tested against the values of the IN expression. In order to set the values of the IN expression, we must use the CriteriaBuilder.In object returned from the in() method. Consider the following JP QL query:

SELECT e
FROM Employee e
WHERE e.address.state IN ('NY', 'CA')

To convert this query to the Criteria API, we must invoke the value() method of the CriteriaBuilder.In interface to set the state identifiers we are interested in querying, like so:

CriteriaQuery<Employee> c = cb.createQuery(Employee.class);
Root<Employee> emp = c.from(Employee.class);
c.select(emp)
 .where(cb.in(emp.get("address")
                 .get("state")).value("NY").value("CA"));

Note the chained invocation of the value() method in order to set multiple values into the IN expression. The argument to in() is the expression to search for against the list of values provided via the value() method.

In cases where there are a large number of value() calls to chain together that are all of the same type, the Expression interface offers a shortcut for creating IN expressions. The in() methods of this interface allow one or more values to be set in a single call.

CriteriaQuery<Employee> c = cb.createQuery(Employee.class);
Root<Employee> emp = c.from(Employee.class);
c.select(emp)
 .where(emp.get("address")
           .get("state").in("NY","CA"));

In this case, the call to in() is suffixed to the expression rather than prefixed as was the case in the previous example. Note the difference in argument type between the CriteriaBuilder and Expression interface versions of in(). The Expression version of in() accepts the values to be searched, not the expression to search for. The in() method of the CriteriaBuilder interface allows more typing options, but for the most part it is largely a case of personal preference when deciding which approach to use.

IN expressions that use subqueries are written using a similar approach. For a more complex example, in the previous chapter, we demonstrated a JP QL query using an IN expression in which the department of an employee is tested against a list generated from a subquery. The example is reproduced here.

SELECT e
FROM Employee e
WHERE e.department IN
  (SELECT DISTINCT d
   FROM Department d JOIN d.employees de JOIN de.project p
   WHERE p.name LIKE 'QA%')

We can convert this example to the Criteria API as shown in Listing 9-6.

Listing 9-6.  IN Expression Using a Subquery

CriteriaQuery<Employee> c = cb.createQuery(Employee.class);
Root<Employee> emp = c.from(Employee.class);
Subquery<Department> sq = c.subquery(Department.class);
Root<Department> dept = sq.from(Department.class);
Join<Employee,Project> project =
    dept.join("employees").join("projects");
sq.select(dept.<Integer>get("id"))
  .distinct(true)
  .where(cb.like(project.<String>get("name"), "QA%"));
c.select(emp)
 .where(cb.in(emp.get("dept").get("id")).value(sq));

The subquery is created separately and then passed into the value() method as the expression to search for the Department entity. This example also demonstrates using an attribute expression as a value in the search list.

Case Expressions

Like the IN expression, building CASE expressions with the Criteria API requires the use of a helper interface. In this example we will convert the examples used in Chapter 8 to the Criteria API, demonstrating general and simple case expressions, as well as COALESCE.

image Tip  Although case statements are required by JPA providers, they may not be supported by all databases. The use of a case statement on a database platform that does not support case expressions is undefined.

We will begin with the general form of the CASE expression, the most powerful but also the most complex.

SELECT p.name,
       CASE WHEN TYPE(p) = DesignProject THEN 'Development'
            WHEN TYPE(p) = QualityProject THEN 'QA'
            ELSE 'Non-Development'
       END
FROM Project p
WHERE p.employees IS NOT EMPTY

The selectCase() method of the CriteriaBuilder interface is used to create the CASE expression. For the general form it takes no arguments and returns a CriteriaBuilder.Case object that we may use to add the conditional expressions to the CASE statement. The following example demonstrates this approach:

CriteriaQuery<Object[]> c = cb.createQuery(Object[].class);
Root<Project> project = c.from(Project.class);
c.multiselect(project.get("name"),
         cb.selectCase()
           .when(cb.equal(project.type(), DesignProject.class),
                 "Development")
           .when(cb.equal(project.type(), QualityProject.class),
                 "QA")
           .otherwise("Non-Development"))
 .where(cb.isNotEmpty(project.<List<Employee>>get("employees")));

The when() and otherwise() methods correspond to the WHEN and ELSE keywords from JP QL. Unfortunately, “else” is already a keyword in Java so “otherwise” must be used as a substitute.

The next example simplifies the previous example down to the simple form of the case statement.

SELECT p.name,
       CASE TYPE(p)
            WHEN DesignProject THEN 'Development'
            WHEN QualityProject THEN 'QA'
            ELSE 'Non-Development'
       END
FROM Project p
WHERE p.employees IS NOT EMPTY

In this case, we pass the primary expression to be tested to the selectCase() method and use the when() and otherwise() methods of the CriteriaBuilder.SimpleCase interface. Rather than a predicate or boolean expression, these methods now accept single-valued expressions that are compared to the base expression of the CASE statement.

CriteriaQuery<Object[]> c = cb.createQuery(Object[].class);
Root<Project> project = c.from(Project.class);
c.multiselect(project.get("name"),
         cb.selectCase(project.type())
           .when(DesignProject.class, "Development")
           .when(QualityProject.class, "QA")
           .otherwise("Non-Development"))
 .where(cb.isNotEmpty(project.<List<Employee>>("employees")));

The last example we will cover in this section concerns the JP QL COALESCE expression.

SELECT COALESCE(d.name, d.id)
FROM Department d

Building a COALESCE expression with the Criteria API requires a helper interface like the other examples we have looked at in this section, but it is closer in form to the IN expression than the CASE expressions. Here we invoke the coalesce() method without arguments to get back a CriteriaBuilder.Coalesce object that we then use the value() method of to add values to the COALESCE expression. The following example demonstrates this approach:

CriteriaQuery<Object> c = cb.createQuery();
Root<Department> dept = c.from(Department.class);
c.select(cb.coalesce()
           .value(dept.get("name"))
           .value(dept.get("id")));

Convenience versions of the coalesce() method also exist for the case where only two expressions are being compared.

CriteriaQuery<Object> c = cb.createQuery();
Root<Department> dept = c.from(Department.class);
c.select(cb.coalesce(dept.get("name"),
                     dept.get("id")));

A final note about case expressions is that they are another exception to the rule that the CriteriaBuilder methods are non-mutating. Each when() method causes another conditional expression to be added incrementally to the case expression, and each value() method adds an additional value to the coalesce list.

Function Expressions

Not to be confused with the built-in functions of JP QL, criteria function expressions are the Criteria API equivalent of the FUNCTION keyword in JP QL. They allow native SQL stored functions to be mixed with other Criteria API expressions. They are intended for cases where a limited amount of native SQL is required to satisfy some requirement but you don’t want to convert the entire query to SQL.

Function expressions are created with the function() method of the CriteriaBuilder interface. It requires as arguments the database function name, the expected return type, and a variable list of arguments, if any, that should be passed to the function. The return type is an Expression, so it can be used in many other places within the query. The following example invokes a database function to capitalize the first letter of each word in a department name:

CriteriaQuery<String> c = cb.createQuery(String.class);
Root<Department> dept = c.from(Department.class);
c.select(cb.function("initcap", String.class, dept.get("name")));

As always, developers interested in maximizing the portability of their applications should be careful in using function expressions. Unlike native SQL queries, which are clearly marked, function expressions are a small part of what otherwise looks like a normal portable JPA query that is actually tied to database-specific behavior.

Downcasting

JPA 2.1 introduced support for type downcasting when querying over an entity inheritance hierarchy via the TREAT operation in JP QL. In the Criteria API, this operation is exposed via the treat() method of the CriteriaBuilder interface. It may be used when constructing joins to limit results to a specific subclass or as part of general criteria expressions in order to access state fields from specific subclasses.

The treat() method has been overloaded to return either Join or Path objects depending on the type of arguments. Recall the example in Chapter 8 that demonstrated using treat() to limit an Employee query to only those employees who are working on a QualityProject for which the quality rating is greater than five:

SELECT e FROM Employee e JOIN TREAT(e.projects AS QualityProject) qp

WHERE qp.qualityRating > 5

The criteria equivalent of this query is as follows:

CriteriaQuery<Employee> q = cb.createQuery(Employee.class);
Root<Employee> emp = q.from(Employee.class);
Join<Employee,QualityProject> project = cb.treat(emp.join(emp.get("projects"), QualityProject.class);
q.select(emp)
  .where(cb.gt(project.<Integer>get("qualityRating"), 5));

In this example, treat() accepts a Join object and returns a Join object and can be referenced in the WHERE clause because it is saved in a variable. When used directly in a criteria expression, such as in a WHERE clause, it accepts a Path or a Root object and returns a Path that corresponds to the subclass requested. For example, we could use the treat() method to access the quality rating or the design phase in a query across projects.

CriteriaQuery<Project> q = cb.createQuery(Project.class);
Root<Project> project = q.from(Project.class);
q.select(project)
  .where(cb.or(
                 cb.gt(cb.treat(project, QualityProject.class). <Integer>get("qualityRating"), 5),
                 cb.gt(cb.treat(project, DesignProject.class). <Integer>get("designPhase"), 3)));

image Tip  Support for downcasting to a subtype was added in JPA 2.1.

Outer Join Criteria

Earlier in the chapter we demonstrated how to use the Criteria API to construct joins between entities. We have also demonstrated how to use the where() method of the AbstractQuery interface and the expression framework of the Criteria API to construct the filtering conditions that will limit the result set. This works fine for inner joins, but as we discussed in the last chapter, outer joins require additional support to establish filtering criteria that still preserves the optionality of the joined entities. Like the where() method of the AbstractQuery interface, the Join interface supports the on() method for specifying outer join criteria that must be evaluated as the result set is being created. This is equivalent to the ON condition of the JP QL JOIN expression. The following is a simple example of using the ON keyword in JP QL:

SELECT e FROM Employee e JOIN e.projects p ON p.name = 'Zooby'

The on() method takes a single predicate object to represent the join condition. The criteria equivalent of the JP QL query above would be expressed as:

CriteriaQuery<Employee> q = cb.createQuery(Employee.class);
Root<Employee> emp = q.from(Employee.class);
Join<Employee,Project> project = emp.join("projects", JoinType.LEFT)
            .on(cb.equal(project.get("name"), "Zooby"));
q.select(emp);

image Tip  Support for the ON condition of outer join queries was added in JPA 2.1.

The ORDER BY Clause

The orderBy() method of the CriteriaQuery interface sets the ordering for a query definition. This method accepts one or more Order objects, which are created by the asc() and desc() methods of the CriteriaBuilder interface, for ascending and descending ordering respectively. The following example demonstrates the orderBy() method:

CriteriaQuery<Tuple> c = cb.createQuery(Tuple.class);
Root<Employee> emp = c.from(Employee.class);
Join<Employee,Department> dept = emp.join("dept");
c.multiselect(dept.get("name"), emp.get("name"));
c.orderBy(cb.desc(dept.get("name")),
          cb.asc(emp.get("name")));

Query ordering through the Criteria API is still subject to the same constraints as JP QL. The arguments to asc() and desc() must be single-valued expressions, typically formed from the state field of an entity. The order in which the arguments are passed to the orderBy() method determines the generation of SQL. The equivalent JP QL for the query shown in the previous example is as follows:

SELECT d.name, e.name
FROM Employee e JOIN e.dept d
ORDER BY d.name DESC, e.name

The GROUP BY and HAVING Clauses

The groupBy() and having() methods of the AbstractQuery interface are the Criteria API equivalent of the GROUP BY and HAVING clauses from JP QL, respectively. Both arguments accept one or more expressions that are used to group and filter the data. By this point in the chapter, the usage pattern for these methods should be more intuitive to you. Consider the following example from the previous chapter:

SELECT e, COUNT(p)
FROM Employee e JOIN e.projects p
GROUP BY e
HAVING COUNT(p) >= 2

To re-create this example with the Criteria API, we will need to make use of both aggregate functions and the grouping methods. The following example demonstrates this conversion:

CriteriaQuery<Object[]> c = cb.createQuery(Object[].class);
Root<Employee> emp = c.from(Employee.class);
Join<Employee,Project> project = emp.join("projects");
c.multiselect(emp, cb.count(project))
 .groupBy(emp)
 .having(cb.ge(cb.count(project),2));

Bulk Update and Delete

The CriteriaBuilder interface is not limited to report queries. Bulk update and delete queries may also be created using the createCriteriaUpdate() and createCriteriaDelete() methods, respectively. Like their report query counterparts, the CriteriaBuilder versions of bulk update and delete queries use the same methods for generating the FROM and WHERE clauses of the query. Methods specific to bulk update and delete operations are encapsulated in the CriteraUpdate and CriteriaDelete interfaces.

image Tip  Although bulk updates and deletes have existed in JP QL since JPA 1.0, support for performing bulk updates and deletes using the Criteria API was not added until JPA 2.1.

Bulk update queries in JP QL use the SET expression to change the state of entities. Mirroring this functionality, the set() method of the CriteriaUpdate query allows the developer to assign expressions constructed from the Criteria API to modify designated entity state fields. In Chapter 8, we demonstrated the following query to allocate a raise to employees associated with a specific project:

UPDATE Employee e
SET e.salary = e.salary + 5000
WHERE EXISTS (SELECT p
              FROM e.projects p
              WHERE p.name = 'Release2')

Using the Criteria API, the same query would be constructed as follows:

CriteriaUpdate<Employee> q = cb.createCriteriaUpdate(Employee.class);
Root<Employee> e = q.from(Employee.class);
Subquery<Project> sq = c.subquery(Project.class);
Root<Employee> sqEmp = sq.correlate(emp);
Join<Employee,Project> project = sqEmp.join("projects");
sq.select(project)
  .where(cb.equal(project.get("name"),"Release2"));
q.set(emp.get("salary"), cb.sum(emp.get("salary"), 5000))
 .where(cb.exists(sq));

The set() method may be invoked multiple times for the same query. It also supports the same invocation chaining capability that is used by other query methods in the Criteria API.

Bulk delete operations are very straightforward with the Criteria API. The following example converts the JP QL DELETE query example from Chapter 8 to use the Criteria API:

DELETE FROM Employee e
WHERE e.department IS NULL

CriteriaDelete<Employee> q = cb.createCriteriaDelete(Employee.class);
Root<Employee> emp = c.from(Employee.class);
q.where(cb.isNull(emp.get("dept"));

If executed, this example will remove all employees that are not assigned to any department.

Strongly Typed Query Definitions

Throughout this chapter, we have been demonstrating Criteria API query definitions constructed using string names to refer to attributes in path expressions. This subset of the Criteria API is referred to as the string-based API. We have mentioned a few times, however, that an alternative approach for constructing path expressions also existed that offered a more strongly typed approach. In the following sections, we will establish some theory around the metamodel that underlies the strongly typed approach and then demonstrate its usage with the Criteria API.

The Metamodel API

Before we look at strongly typed query definitions, we must first set the stage for our discussion with a short digression into the metamodel for persistence units in a JPA application. The metamodel of a persistence unit is a description of the persistent type, state, and relationships of entities, embeddables, and managed classes. With it, we can interrogate the persistence provider runtime to find out information about the classes in a persistence unit. A wide variety of information, from names to types to relationships, is stored by the persistence provider and made accessible through the metamodel API.

The metamodel API is exposed through the getMetamodel() method of the EntityManager interface. This method returns an object implementing the Metamodel interface which we can then use to begin navigating the metamodel. The Metamodel interface can be used to list the persistent classes in a persistence unit or to retrieve information about a specific persistent type.

For example, to obtain information about the Employee class we have been demonstrating in this chapter, we would use the entity() method.

Metamodel mm = em.getMetamodel();
EntityType<Employee> emp_ = mm.entity(Employee.class);

The equivalent methods for embeddables and managed classes are embeddable() and managedType(), respectively. It is important to note that the call to entity() in this example is not creating an instance of the EntityType interface. Rather it is retrieving a metamodel object that the persistence provider would have initialized when the EntityManagerFactory for the persistence unit was created. Had the class argument to entity() not been a pre-existing persistent type, an IllegalArgumentException would have been thrown.

The EntityType interface is one of many interfaces in the metamodel API that contain information about persistent types and attributes. Figure 9-2 shows the relationships between the interfaces that make up the metamodel API.

9781430249269_Fig09-02.jpg

Figure 9-2. Metamodel interfaces

To further expand on this example, consider a tool that inspects a persistent unit and prints out summary information to the console. To enumerate all of the attributes and their types, we could use the following code:

public <T> void listAttributes(EntityType<T> type) {
    for (Attribute<? super T, ?> attr : type.getAttributes()) {
        System.out.println(attr.getName() + " " +
                           attr.getJavaType().getName() + " " +
                           attr.getPersistentAttributeType());
    }
}

For the Employee entity, this would result in the following:

id int BASIC
name java.lang.String BASIC
salary float BASIC
dept com.acme.Department MANY_TO_ONE
address com.acme.Address MANY_TO_ONE
directs com.acme.Employee ONE_TO_MANY
phones com.acme.Phone ONE_TO_MANY
projects com.acme.Project ONE_TO_MANY

In just a few method calls, we have uncovered a lot of information about the Employee entity. We have listed all of the attributes and for each one we now know the attribute name, class type, and persistent type. Collections have been unwrapped to reveal the underlying attribute type, and the various relationship types are clearly marked.

From the perspective of the Criteria API and providing strongly typed queries, we are mostly interested in the type information exposed by the metamodel API interfaces. In the next section, we will demonstrate how it can be used in that context.

The metamodel of a persistence unit is not a new concept. Previous versions of JPA have always maintained similar structures internally for use at runtime, but only with JPA 2.0 was this kind of metamodel exposed directly to developers. Direct usage of the metamodel classes is somewhat specialized, but, for tool developers or applications that need to work with a persistence unit in a completely generic way, the metamodel is a useful source of information about persistent types.

Strongly Typed API Overview

The string-based API within the Criteria API centers around constructing path expressions: join(), fetch(), and get() all accept string arguments. The strongly typed API within the Criteria API also supports path expressions by extending the same methods, but is also present in all aspects of the CriteriaBuilder interface, simplifying typing and enforcing type safety where possible.

The strongly typed API draws its type information from the metamodel API classes we introduced in the previous section. For example, the join() method is overloaded to accept arguments of type SingularAttribute, CollectionAttribute, SetAttribute, ListAttribute, and MapAttribute. Each overloaded version uses the type information associated with the attribute interface to create the appropriate return type such as MapJoin for arguments of type MapAttribute.

To demonstrate this behavior, we will revisit an example from earlier in the chapter where we were forced to use joinMap() with the string-based API in order to access the MapJoin object. This time we will use the metamodel API to obtain entity and attribute type information and pass it to the Criteria API methods.

CriteriaQuery<Object> c = cb.createQuery();
Root<Employee> emp = c.from(Employee.class);
EntityType<Employee> emp_ = emp.getModel();
MapJoin<Employee,String,Phone> phone =
    emp.join(emp_.getMap("phones", String.class, Phone.class));
c.multiselect(emp.get(emp_.getSingularAttribute("name", String.class)),
              phone.key(), phone.value());

There are several things to note about this example. First is the use of getModel(). This method exists on many of the Criteria API interfaces as a shortcut to the underlying metamodel object. We assign it to a variable, emp_, and add an underscore by convention to help denote it as a metamodel type. Second are the two calls to methods from the EntityType interface. The getMap() invocation returns the MapAttribute object for the phones attribute while the getSingularAttribute() invocation returns the SingularAttribute object for the name attribute. Again, we have to supply the type information for the attribute, partly to satisfy the generic type requirements of the method invocation but also as a type checking mechanism. Had any of the arguments been incorrect, an exception would have been thrown. Also note that the join() method no longer qualifies the collection type yet returns the correct MapJoin instance. The join() method is overloaded to behave correctly in the presence of the different collection attribute interfaces from the metamodel API.

The potential for error in using the metamodel objects is actually the heart of what makes it strongly typed. By enforcing that the type information is available, the Criteria API is able to ensure that not only are the appropriate objects being passed around as method arguments but also that compatible types are used in various expression building methods.

There is no question, however, that this is a much more verbose way of constructing a query. Fortunately, the JPA 2.0 specification also defines an alternate presentation of the persistence unit metamodel that is designed to make strongly typed programming easier. We will discuss this model in the next section.

The Canonical Metamodel

Our usage of the metamodel API so far has opened the doors to strong type checking but at the expense of readability and increased complexity. The metamodel APIs are not complex, but they are verbose. To simplify their usage, JPA also provides a canonical metamodel for a persistence unit.

The canonical metamodel consists of dedicated classes, typically generated, one per persistent class, that contain static declarations of the metamodel objects associated with that persistent class. This allows you to access the same information exposed through the metamodel API, but in a form that applies directly to your persistent classes. Listing 9-7 shows an example of a canonical metamodel class.

Listing 9-7.  The Canonical Metamodel Class for Employee

@StaticMetamodel(Employee.class)
public class Employee_ {
    public static volatile SingularAttribute<Employee, Integer> id;
    public static volatile SingularAttribute<Employee, String> name;
    public static volatile SingularAttribute<Employee, String> salary;
    public static volatile SingularAttribute<Employee, Department> dept;
    public static volatile SingularAttribute<Employee, Address> address;
    public static volatile CollectionAttribute<Employee, Project> project;
    public static volatile MapAttribute<Employee, String, Phone> phones;
}

Each canonical metamodel class contains a series of static fields, one for each attribute in the persistent class. Each field in the canonical metamodel class is of the metamodel type that corresponds to the type of the like-named field or property in the persistent class. If a persistent field or property in an entity is of a primitive type or a single-valued relationship, then the like-named field in the canonical metamodel class will be of type SingularAttribute. Similarly, if a persistent field or property is collection-valued, then the field in the canonical metamodel class will be of type ListAttribute, SetAttribute, MapAttribute, or CollectionAttribute, depending upon the type of collection. Additionally, each canonical metamodel class is annotated with @StaticMetamodel, which identifies the persistent class it is modeling.

A canonical metamodel class is generated in the same package as its associated persistent class and has the same name, but with an additional underscore suffix. Non-canonical metamodel classes may be generated in other packages and with different names if there is a need to do so. Some generation tools may provide these kinds of options. The @StaticMetamodel annotation provides the binding between the metamodel class and the entity, not the name or package, so there is no standard way of reading in such metamodels. If a provider tool can generate the concrete metamodel classes in some non-canonical form, then that runtime might be needed to recognize or detect them as well.

Using the Canonical Metamodel

We are now in the position of being able to leverage the metamodel without actually using the metamodel API. As an example, we can convert the example from the previous section to use the statically generated canonical classes.

CriteriaQuery<Object> c = cb.createQuery();
Root<Employee> emp = c.from(Employee.class);
MapJoin<Employee,String,Phone> phone = emp.join(Employee_.phones);
c.multiselect(emp.get(Employee_.name), phone.key(), phone.value());

This is a much more concise approach to using the metamodel objects than the interfaces we discussed earlier while offering the exact same benefits. Coupled with a development environment that has good code completion features, you may find it more convenient to develop using the canonical metamodel than with the string-based API.

We can convert a more complex example to illustrate using the canonical metamodel. Listing 9-8 converts the example in Listing 9-6, showing an IN expression that uses a subquery, from using the string-based attribute names to using the strongly typed approach.

Listing 9-8.  Strongly Typed Query

CriteriaQuery<Employee> c = cb.createQuery(Employee.class);
Root<Employee> emp = c.from(Employee.class);
Subquery<Department> sq = c.subquery(Department.class);
Root<Department> dept = sq.from(Department.class);
Join<Employee,Project> project =
    dept.join(Department_.employees).join(Employee_.projects);
sq.select(dept.get(Department_.id))
  .distinct(true)
  .where(cb.like(project.get(Project_.name), "QA%"));
c.select(emp)
 .where(cb.in(emp.get(Employee_.dept).get(Department_.id)).value(sq));

Note that there are two main differences between this example and Listing 9-6. First, the use of typed metamodel static fields to indicate entity attributes helps avert typing errors in attribute strings. Second, there is no longer the need to do inlined typing to convert the Path<Object>, returned from the get() method, to a narrower type. The stronger typing solves that problem for us.

All the examples in this chapter can be easily converted to use the canonical metamodel classes by simply changing the string-based attributes to the corresponding static fields of the generated metamodel classes. For example, emp.get("name") can be replaced with emp.get(Employee_.name), and so on.

Generating the Canonical Metamodel

If you choose to use the generated metamodel in your queries, you should be aware of some of the details of the development process in case inconsistency or configuration issues crop up. The canonical metamodel classes will need to be updated or regenerated when certain changes to entities have occurred during development. For example, changing the name of a field or property, or changing its shape, would require an updated canonical metamodel class for that entity.

The generation tools offered by providers may vary widely in function or in operation. Generation may involve reading the persistence.xml file, as well as accessing annotations on entities and XML mapping files to determine what the metamodel classes should look like. Since the specification does not require such tools to even exist, a provider may choose to not support it at all, expecting that if developers want to use the canonical metamodel classes they will handcode them. Most providers do offer some kind of generation tool, though; it’s just a matter of understanding how that vendor-specific tool works. It might run statically as a separate command line tool, or it might use the compiler hook offered in the JDK (starting in Java SE 6) to look at the entities and generate the classes at compile-time. For example, to run the command line mode of the tool shipped with the EclipseLink Reference Implementation, you could set the javac -processor and -proc:only options. These two options indicate the EclipseLink code/annotation processor1 for the compiler to invoke, and instruct the compiler to call only the processor but not do any actual compilation.

javac -processor org.eclipse.persistence.internal.jpa.modelgen.CanonicalModelProcessor
         -proc:only
         -classpath lib/*.jar;punit
         *.java

The options are on separate lines to make them easier to see. It is assumed that the lib directory contains the necessary EclipseLink JAR and JPA interface JAR, and that the META-INF/persistence.xml is in the punit directory.

Metamodel generation tools will also typically run in an IDE, and there will likely be IDE-specific configuration necessary to direct the incremental compiler to use the tool’s annotation processor. In some IDEs, there must be an additional code/annotation processor JAR to configure. The generated metamodel classes will need to go in a specific directory and be on the build classpath so the criteria queries that reference them can compile. Consult the IDE help files on how annotation processors or APT is supported, as well as the provider documentation on what to configure in order to enable generation in a given IDE.

Choosing the Right Type of Query

Now that you are familiar with criteria queries, you are well armed with two of the three distinct languages in which to create queries: JP QL, native SQL, and the Criteria API. We have demonstrated that the Criteria API is relatively easy to understand and use, but when should it be used? The answer is a combination of your own preferences as a developer and the capabilities of the different query approaches.

Native SQL queries are an easy choice to make: either you need to access a vendor-specific feature or you don’t. If you do, there is only one option available if you can’t work around the dependency. You discovered in this chapter that JP QL and the Criteria API are almost completely equivalent in functionality. When should you use a text-based query definition over one created from programming APIs?

The programming API flows smoothly from the application. It can be strongly typed to reduce the chance of errors, and with the code completion features of most modern development environments it can be relatively quick to put together. It is also ideal for cases where the query definition can’t be fully constructed with the input of a user.

JP QL, on the other hand, is more concise and familiar to anyone experienced with SQL. It can also be embedded with the application annotations or XML descriptors and maintained independently of the regular programming process. Some development environments also offer visual builders for JP QL queries, making it a seamless part of the development process.

There is no right answer when it comes to choosing between JP QL and the Criteria API. They can be mixed and matched within an application as you see fit. In general, however, we still encourage developers to use named queries as much as possible, and, for this, JP QL is the ideal choice.

Summary

In this chapter we have taken a tour of the Criteria API. We started with an overview and example focused on dynamic query creation.

In investigating the Criteria API, we started with JP QL concepts and sought to draw parallels between the query language and the Criteria API. We looked at how to formulate each clause of a query with the Criteria API and addressed some of the more complex issues that developers encounter.

We introduced the metamodel API and showed how it can be used to create strongly typed queries with the Criteria API. We looked at programming with the metamodel API using both runtime interfaces and through the generated classes of a canonical metamodel implementation.

Finally we discussed the advantages of the different approaches to building queries using JP QL, native SQL, and the Criteria API. We highlighted some of the strengths of the query languages and presented some advice on when one might be more beneficial. We reiterated that the right query language for you will mostly come down to style and personal preference.

Over the course of the chapter, we have tried to demonstrate that although the form of the Criteria API is very different from the JP QL query language, the underlying semantics are almost identical and switching between the two is relatively easy once you get the hang of the API coding patterns.

In the next chapter we switch back to object-relational mapping and cover advanced concepts such as inheritance, composite primary keys and associations, and multiple table mappings.

1 This is often referred to as an annotation processing tool, or APT, because it used to be a standalone tool shipped with the JDK and used only for processing annotations. Since Java SE 6, it is actually a generalized compile-time hook to perform any kind of pre-processing or code generation.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.147.238.70