The Criteria API

One of the main additions to JPA in the 2.0 specification was the introduction of the Criteria API. The Criteria API is meant as a complement to JPQL.

Although JPQL is very flexible, it has some problems that make working with it more difficult than necessary. For starters, JPQL queries are stored as strings, and the compiler has no way of validating the JPQL syntax. Additionally, JPQL is not type-safe: we could write a JPQL query in which our where clause could have a string value for a numeric property and our code would compile and deploy just fine.

To get around the JPQL limitations described in the previous paragraph, the Criteria API was introduced to JPA in version 2.0 of the specification. The Criteria API allows us to write JPA queries programmatically, without having to rely on JPQL.

The following code example illustrates how to use the Criteria API in our Java EE applications:

package net.ensode.javaee8book.criteriaapi.namedbean; 
 
import java.util.List; 
import javax.enterprise.context.RequestScoped; 
import javax.inject.Named; 
import javax.persistence.EntityManager; 
import javax.persistence.PersistenceContext; 
import javax.persistence.TypedQuery; 
import javax.persistence.criteria.CriteriaBuilder; 
import javax.persistence.criteria.CriteriaQuery; 
import javax.persistence.criteria.Path; 
import javax.persistence.criteria.Predicate; 
import javax.persistence.criteria.Root; 
import javax.persistence.metamodel.EntityType; 
import javax.persistence.metamodel.Metamodel; 
import javax.persistence.metamodel.SingularAttribute; 
import net.ensode.javaee8book.criteriaapi.entity.UsState; 
 
@Named 
@RequestScoped 
public class CriteriaApiDemoBean { 
 
    @PersistenceContext 
    private EntityManager entityManager; 
 
    private List<UsState> matchingStatesList; 
    private List<UsState> matchingStatesList; 
 
    public String findStates() { 
        String retVal = "confirmation"; 
        try { 
            CriteriaBuilder criteriaBuilder = entityManager.                     
getCriteriaBuilder();

CriteriaQuery<UsState> criteriaQuery = criteriaBuilder.

createQuery(UsState.class);

Root<UsState> root = criteriaQuery.from(UsState.class);

Metamodel metamodel = entityManager.getMetamodel();

EntityType<UsState> usStateEntityType =
metamodel.entity(

UsState.class);

SingularAttribute<UsState, String> usStateAttribute
=
usStateEntityType.getDeclaredSingularAttribute(

"usStateNm",

String.class);

Path<String> path = root.get(usStateAttribute);

Predicate predicate = criteriaBuilder.like(
path,
"New%");

criteriaQuery = criteriaQuery.where(predicate);

TypedQuery typedQuery = entityManager.createQuery(

criteriaQuery);

matchingStatesStream = typedQuery.getResultStream();

if (matchingStatesStream != null) {

matchingStatesList =
matchingStatesStream.collect(Collectors.toList());

}
} catch (Exception e) { retVal = "error"; e.printStackTrace(); } return retVal; } public List<UsState> getMatchingStatesList() { return matchingStatesList; } public void setMatchingStatesList(List<UsState>
matchingStatesList) { this.matchingStatesList = matchingStatesList; } }

The preceding example is equivalent to the JPQL example we saw earlier in this chapter. This example, however, takes advantage of the Criteria API instead of relying on JPQL.

When writing code using the Criteria API, the first thing we need to do is obtain an instance of a class implementing the javax.persistence.criteria.CriteriaBuilder interface; as we can see in the preceding example, we need to obtain this instance by invoking the getCriteriaBuilder() method on our EntityManager.

From our CriteriaBuilder implementation, we need to obtain an instance of a class implementing the javax.persistence.criteria.CriteriaQuery interface. We do this by invoking the createQuery() method in our CriteriaBuilder implementation. Notice that CriteriaQuery is generically typed. The generic type argument dictates the type of result that our CriteriaQuery implementation will return upon execution. By taking advantage of generics in this way, the Criteria API allows us to write type-safe code.

Once we have obtained a CriteriaQuery implementation, from it we can obtain an instance of a class implementing the javax.persistence.criteria.Root interface. The root implementation dictates which JPA Entity we will be querying from. It is analogous to the FROM query in JPQL (and SQL).

The next two lines in our example take advantage of another new addition to the JPA specification: the Metamodel API. In order to take advantage of the Metamodel API, we need to obtain an implementation of the javax.persistence.metamodel.Metamodel interface by invoking the getMetamodel() method on our EntityManager.

From our Metamodel implementation, we can obtain a generically typed instance of the javax.persistence.metamodel.EntityType interface. The generic type argument indicates the JPA entity our EntityType implementation corresponds to. EntityType allows us to browse the persistent attributes of our JPA entities at runtime. This is exactly what we do in the next line in our example. In our case, we get an instance of SingularAttribute, which maps to a simple, singular attribute in our JPA entity. EntityType has methods to obtain attributes that map to collections, sets, lists, and maps. Obtaining these attribute types is very similar to obtaining a SingularAttribute, therefore we won't be covering those directly. Please refer to the Java EE 8 API documentation at https://javaee.github.io/javaee-spec/javadocs/ for more information.

As we can see in our example, SingularAttribute contains two generic type arguments. The first argument dictates the JPA entity we are working with, and the second one indicates the type of the attribute. We obtain our SingularAttribute by invoking the getDeclaredSingularAttribute() method on our EntityType implementation and passing the attribute name (as declared in our JPA entity) as a string.

Once we have obtained our SingularAttribute implementation, we need to obtain an import javax.persistence.criteria.Path implementation by invoking the get() method in our Root instance, and passing our SingularAttribute as a parameter.

In our example, we will get a list of all the new states in the United States; that is, all states whose names start with New. This, of course, is a job for a "like" condition. We can do this with the criteria API by invoking the like() method on our CriteriaBuilder implementation. The like() method takes our Path implementation as its first parameter, and the value to search for as its second parameter.

CriteriaBuilder has a number of methods that are analogous to SQL and JPQL clauses, such as equals(), greaterThan(), lessThan(), and(), and or() (for the complete list, refer to the Java EE 8 documentation at https://javaee.github.io/javaee-spec/javadocs/). These methods can be combined to create complex queries via the Criteria API.

The like() method in CriteriaBuilder returns an implementation of the javax.persistence.criteria.Predicate interface, which we need to pass to the where() method in our CriteriaQuery implementation. This method returns a new instance of CriteriaBuilder, which we assign to our criteriaBuilder variable.

At this point we are ready to build our query. When working with the Criteria API, we deal with the javax.persistence.TypedQuery interface, which can be thought of as a type-safe version of the Query interface we use with JPQL. We obtain an instance of TypedQuery by invoking the createQuery() method in EntityManager, and passing our CriteriaQuery implementation as a parameter.

To obtain our query results as a list, we simply invoke getResultList() on our TypedQuery implementation. It is worth reiterating that the Criteria API is type-safe, therefore attempting to assign the results of getResultList() to a list of the wrong type would result in a compilation error.

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

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