Wednesday, January 26, 2011

JPA2 Criteria API

In the process of writing the cabin finder application, I wanted to have a search panel where you could enter amenities such as fireplace, hottub, firepit, price range, etc. and update content panel with a google maps view of the matching cabins.  First I started with the old JPAQL way of writing a entityManager.createQuery("select c from cabins c where .......").   Then I came across the new way of doing it.  The Critieria API.

What's so great about Critieria API?  Well 2 things for starters.  First, queries are strongly typed in that the don't return a List of untyped objects.  Now they return a List<Cabin> or whatever root object type you set.  Second, they query syntax used to be a String, now it is a set of builder commands that can be validated by the compiler with the use of MetaData.

Step 1 is to setup MetaData.  If you're using eclipse, this is pretty simple.  Goto project properties and set the project facet for JPA version 2.  The additional configuration options allow you to select the JPA provider and download a user library of the JPA provider jars.  I'm using eclipselink since it's native to Glassfish and helps keep my war file small (~5mb so far of which 4.2 are the db drivers for derby and mysql).  Once the user library and JPA provider are setup, eclipse will scan your project and create the JPA  metadata.  I put mine in the .apt_generated folder.  Once created you can review the metadata.  Mostly you'll see SingularAttribute or ListAttributs for simple and list types respectively.  Also note the class names are followed with and inderscore, so Cabin entity gets Cabin_ metadata created.

Step 2 is to start using the Criteria API.  The example to search for amenities such as fireplace, hottub and/or firepit is pretty straightforward.  First, get a CriteriaBuilder from the entityManager.  Then set the class type you are expecting the query to return.  The "where" part of the query is defined by Predicates so we'll initialize one and add each submitted criteria one at a time.  Then set the query's where to the predicate and get the results list.



 private List buildAndRunQuery() {
  List retVal = null;
  CriteriaBuilder builder = db.getCriteriaBuilder();
  CriteriaQuery query = builder.createQuery(Cabin.class);
  Root root = query.from(Cabin.class);

  Predicate temp = builder.conjunction();;
  if(cabin.getAddress().getState()!=null) {
   Join address = root.join( Cabin_.address );
   temp = builder.and(temp,builder.equal(address.get(Address_.state),cabin.getAddress().getState()));
  }
  if (cabin.isFirePit()) {
   temp = builder.and(temp,builder.isTrue(root.get(Cabin_.firePit)));
  }
  if (cabin.isFirePlace()) {
   temp = builder.and(temp,builder.isTrue(root.get(Cabin_.firePlace)));
  }
  if (cabin.isHotTub()) {
   temp = builder.and(temp,builder.isTrue(root.get(Cabin_.hotTub)));
  }
  query.where(temp);
  
  retVal = db.createQuery(query).getResultList();  
  return retVal;
 }

Here's a few references that I found to be helpful.
http://docs.jboss.org/hibernate/stable/entitymanager/reference/en/html/querycriteria.html
http://wiki.eclipse.org/EclipseLink/Examples/JPA/JSF_Tutorial#Using_JPA_Queries
http://stackoverflow.com/questions/2510106/dynamic-jpa-2-0-query-using-criteria-api
and
http://stackoverflow.com/questions/2880209/jpa-findbyexample

The source code for the cabin finder is on GitHub at https://github.com/sbasinge/primetest.

So give the Criteria API a shot and have validated, type safe and fast queries!

No comments:

Post a Comment