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!
JPA Criteria API
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.
Generating the JPA Criteria Meta Model Classes
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.
How to use the JPA Critiera API
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:
|Component||Description||How to create|
|Select||The objects or object fields you wish to return. Pretty much like the select part of a JQL query. You can do aggregations here too and return fields from objects but in this brief tutorial we will just select objects.||CriteriaQuery.select|
|From||This is where we stipulate which entity (table) we are quering. You can also follow relationships to other entities that are part of the root entity i.e "joins" and also subSelects.||CriteriaQuery.from|
|Where||This is the where you stipulate the criteria you wish to apply to the entities that you are selecting. You create "Predicates" which are used to build up the "where" clause.||
|Order By||If you wish to stipulate the order object should be returned in, you do it here||CriteriaQuery.orderBy|
|Group By||For aggregations you stipulate the object fields or objects here.||CriteriaQuery.groupBy|
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:
- Create your CriteriaBuilder and CriteriaQuery objects,
- Setup your from clause,
- Setup your select clause,
- Setup your criterias or predicates
- Setup the where clause using your predicates
- Execute the query.
A Simple CriteriaQuery Example
Lets assume we have a simple object called MyEntity which has the following fields:
- dateCreated - Date, and
- age - Integer
Here is a simple CriteiraQuery example.
CriteriaBuilder cb = em.getCriteriaBuilder(); //Step 1
CriteriaQuery cqry = em.createQuery(); //Step 1
//Interesting stuff happens here
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.
Criteria Query Using Joins
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:
- name - String
- enabled - boolean
In addition we extend the MyEntity object to have a reference to AnotherEntity object. So MyEntity becomes:
- dateCreated - Date,
- age - Integer and
- anotherEntity - AnotherEntity
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:
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!
More Complex Select Clause
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: