JPA 2 Criteria API Tutorial
Get your cup of Java
Submitted by Mark Clarke on Sun, 07/10/2011 - 14:00
When JPA 2 was released in 2009 it included the new criteria API. The purpose of the API was to get away from using JQL strings , (JPA Query Language), in your code. Although JQL seems like a great way to leverage your existing SQL knowledge ,in the OO world it has a major drawback namely; there is no compile time checking of your query strings. The first time you find out about a spelling or syntactical error in your query string is at run time. This can be quiet a productivity drain with developers having to correct, compile and redeploy to continue.
Unit testing your code goes some way to addressing this problem but one area that cannot be addressed by unit tests is refactoring. Most refactoring tools battle with strings and you are stuck with rerunning unit tests and correcting each string that slip through the manual changes on each iteration of the test until all is well. Now with the JPA criteria API it's possible to have type safe queries that are checked at compile time and refactoring is much more efficient!
Java: Elevate your programming with our fundamental Java Training courses. Spring: Unlock enterprise Java skills with our focused Spring Framework training
There is a price to pay for this static checking though. First you need to generate a set of meta model classes, more on what these are is given below, that describe the fields of your entities; luckily this is easily achieved by placing a step in your maven build process. The second price you pay is verbosity and a less than intuitive-at-first-glance api.
To generate the meta model classes during your build process add the following plugin details to your maven pom.xml. In my case, because I have a different persistence unit for our unit tests I had to specify which persistence unit I wanted built, otherwise a bug causes the plugin to throw an error when it finds an entity listed in two persistence units. (i.e the production config and test unit config). I make use of Eclipselink in the pom.xml below.
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-source-plugin</artifactId> </plugin> <plugin> <groupId>org.bsc.maven</groupId> <artifactId>maven-processor-plugin</artifactId> <version>1.3.5</version> <executions> <execution> <id>process</id> <goals> <goal>process</goal> </goals> <phase>generate-sources</phase> <configuration> <compilerArguments>-Aeclipselink.persistencexml=src/main/resources/META-INF/persistence.xml -Aeclipselink.persistenceunits=mypu</compilerArguments> <processors> <processor>org.eclipse.persistence.internal.jpa.modelgen.CanonicalModelProcessor</processor> </processors> </configuration> </execution> </executions> </plugin>
Now, at compile time, the set of meta model classes should be automatically generated for you.
The criteria api can seem quiet daunting at first but it's not that bad once you grok its basic design approach. There are two main object that you will use to create your query, namely the CriteriaBuilder object and a CriteriaQuery object. The first step is to get a handle to a CriteriaBuilder object and then create a CriteriaQuery object. This is is done with the following boiler plate code, where em is an EntityManager object.
CriteriaBuilder cb = em.getCriteriaBuilder(); CriteriaQuery cqry = em.createQuery();
From the CriteriaQuery object you will create the four "components" of a query namely:
Criteria Components
CriteriaQuery.where
CriteriaBuilder.equals CriteriaBuilder.lessThanOEquals etc.
In practise nearly every method from the CriteriaQuery and CriteriaBuilder takes an object that implements the interface java.persistence.criteria.Expression, Selection or Predicate, which can be very unhelpful when deciding what to send into the method for a beginner; especially since a lot of the objects implement both interfaces and the Expression interface inherits from the Selection interface! (Does that make your brain hurt? I am sure the API desinger must have had an anuerism).
It really doesn't make sense that a Path object can be sent to the where method. But it best not to pay too much attention to these high level interfaces and understand the process of creating a CritieriaQuery, at least in the beginning.
The simplest approach to understanding the API is to separate out the task of creating a query into the following steps:
Lets assume we have a simple object called MyEntity which has the following fields:
Here is a simple CriteiraQuery example.
//Boilerplate CriteriaBuilder cb = em.getCriteriaBuilder(); //Step 1 CriteriaQuery cqry = em.createQuery(); //Step 1 //Interesting stuff happens here Root<MyEntity> root = cqry.from(MyEntity.class); //Step 2 cqry.select(root); Step 3 //Boilerplate code Query qry = em.createQuery(cqry); //Step 6 List<MyEnity> results = qry.getResultList(); //Step 6
So we have two boilerplate sections that you need to type up, section 1 and section 6. Section 1 creates the criteria objects you need and the section 6 , at the end, executes the query.
The main work happens in the middle, steps 2 - 5, where you construct your query. The simple example above will return all entities in the table mapped to the MyEntity class. We had to tell the query which entity we were selecting from with the "from" method, and then what we wanted returned from the query with the "select" method.
Using our step-by-step approach we can begin to build more complicated queries. Lets say we want to use some criteria in the query. To do this we need to populate the "where" method of the CriteiraQuery with "Predicate" objects.
A predicate object is usually created by using one of the methods on the CritieraBuilder class, although the Expression interface also creates some Predicates, but for now just think of using the CriteriaBuilder class for this task. So lets say we want to find all MyEntity objects with age > 10.
Root<MyEntity> root = cqry.from(MyEntity.class); //Step 2 cqry.select(root); //Step 3 Predicate pGtAge = cb.gt(root.get("age"),10); //Step 4 cqry.where(pGtAge); //Step 5
When creating a predicate we have to tell the API which field of which object we are comparing against. We may have more than one entity that we are quering across, as with a join for example, so you need a reference to the entity being queried.
The object returned by the "from" method above is what we use here. We then use the "get" method to stipulate which field from the entity object we want to compare against. Yes. the API is sadly very verbose.
If we wanted to string more than one criteria together , lets say age >10 and dateCreated>"2011-07-01" then we would create two Predicates and "and" them as follows:
//assume we have created a date object for 2011-07-01 //called date Root<MyEnity> root = cqry.from(MyEntity.class); //Step 2 cqry.select(root); //Step 3 Predicate pGtAge = cb.gt(root.get("age"),10); //Step 4 Predicate pGtDateCreated= cb.greaterThan(root.get("dateCreated"),date); //Step 4 Predicate pAnd = cb.and(pGtDateCreated,pGtAge); //Step 4 cqry.where(pAnd); //Step 5
Using the many methods on CriteriaBuilder you can build up sophisticated "where" clauses. Use the autocomplete on your IDE to see what methods are available or check out the API docs.
You may be wondering why we are using strings in our "Predicate" objects. Afterall doesn't this defeat the purpose of not using JQL? Are CriteriaQueries subject to the same failings then as JQL queries that we setout in the introduction?
The answer to that question is to use the Meta Model Classes that we created in the beginning of this tutorial. Once the classes have been generated you can refer to fields in Entity objects using the meta model classes. The meta model class has the same name as your Entity class with an underscore(_) appeneded. So to say we are comparing against the age field we would use MyEntity_.age. The query above is rewritten below using the meta model classes.
//assume we have created a date object for 2011-07-01 //called date Root<MyEnity> root = cqry.from(MyEntity.class); //Step 2 cqry.select(root); //Step 3 Predicate pGtAge = cb.gt(root.get(MyEntity_.age),10); //Step 4 Predicate pGtDateCreated= cb.greaterThan(root.get(MyEntity_.dateCreated),date); //Step 4 Predicate pAnd = cb.and(pGtDateCreated,pGtAge); //Step 4 cqry.where(pAnd); //Step 5
So what is we want to query across entities i.e do a "join" query? Lets say we have another Entity object called AnotherEntity with fields as follows:
In addition we extend the MyEntity object to have a reference to AnotherEntity object. So MyEntity becomes:
Now lets say we want to query for all MyEntity objects that have an AnotherEntity object which are disabled. We would do this as follows:
Root<MyEnity> root = cqry.from(MyEntity.class); //Step 2 Join<MyEntity,AnotherEntity> join = root.join(MyEntity_.anotherEntity); //Step 2 //Join<MyEntity,AnotherEntity> join = root.join("anotherEntity"); //Step 2 cqry.select(root); //Step 3 Predicate pGtAge = cb.gt(root.get(MyEntity_.age),10); //Step 4 Predicate pGtDateCreated= cb.greaterThan(root.get(MyEntity_.dateCreated),date); //Step 4 Predicate pEqEnabled = cb.equals(join.get(AnotherEntity_.enabled),false); Predicate pAnd = cb.and(pGtDateCreated,pGtAge,pEqEnabled); //Step 4 cqry.where(pAnd); //Step 5
You can see that our form step is getting more complex as we stipulate what to join on. As with the Predicates one can use string or the meta model classses to stipulate the desired field to join on. If you going through the hassle of using the criteria API then there really is no point in using strings!
We will now look at more complex select clauses, our step 2. Lets say we are only interested in the dateCreated field of our MyEntity object. Our code would look something like:
Root<MyEnity> root = cqry.from(MyEntity.class); //Step 2 cqry.select(root.get(MyEntity_.dateCreated)); //Step 3
If we wanted to use an aggregate function and get the minimum dateCreated we could use something like:
Root<MyEnity> root = cqry.from(MyEntity.class); //Step 2 Expression min = cb.min(root.get(MyEntity_.dateCreated));//Step3 cqry.select(min); //Step 3
There are method for returning array of objects or tuples from a query and also a way to create new object on the fly from fields or queries entities. I may cover these in a future tutorial but for now this should be enough to get you going on the Criteria API.
As you start using the Criteria API from JPA2 you will see that there are other ways of creating or manipulating some of the "components" we set out above other than following the step-by-step approach, but if you ever get lost just follow the steps.