JPA 2.0 Criteria API with Maven and EclipseLink

For those of you who have followed the JPA 2.0 specification, you might have come across the new Criteria API. The main idea of this new API is to be able to write a JPQL query using an object-oriented way (through a rich API) instead of a String. For example, if you want to return the list of all customers who are called John, you will write the following JQPL query :

SELECT c FROM Customer c WHERE c.firstName = 'John'

This is a String and many typos can be made. For example you could have typos on JPQL keywords (SLECT instead of SELECT), class names or attributes. You can also write a syntactically incorrect statement (SELECT c WHERE c.firstName = 'John' FROM Customer). And when do you discover that your statement is incorrect ? At runtime ! JPA 2.0 comes with a rich criteria API (in the package javax.persistence.criteria) allowing you to write any JQPL query in an object and syntactically correct way (at compile time). All JPQL keywords (SELECT, FROM, WHERE, LIKE, GROUP BY…) are defined in this API. So the previous JPQL statement can be written as follow (em being the Entity Manager) :

CriteriaBuilder queryBuilder = em.getCriteriaBuilder();
CriteriaQuery<Customer> queryDefinition = queryBuilder.createQuery(Customer.class);
Root<Customer> customer = queryDefinition.from(Customer.class);
queryDefinition.select(customer).where(queryBuilder.equal(customer.get("firstName"), "John"));

As you can see, this statement is not completely type safe :  the attribute firstName is a String. JPA 2.0 comes with this idea of meta-model. Each entity has a static meta-model class generated by JPA describing its meta-data. The convention is that each entity X will have a meta-data class called X_ (with an underscore). So, the Customer entity will have its representation described in Customer_. You can then write the previous query in a type safe (Customer_.firstName) way as follow :

CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder();
CriteriaQuery criteriaQuery = criteriaBuilder.createQuery(Customer.class);
Root customer = criteriaQuery.from(Customer.class);
criteriaQuery.select(customer).where(criteriaBuilder.equal(customer.get(Customer_.firstName), "John"));

How do you get these meta data ? With annotation processor. Here is how you can configure with Intellij IDEA and Maven.

Intellij IDEA

Intellij Idea can help you to generate the meta-model classes on the fly. You need to configure the EclipseLink annotation processor (org.eclipse.persistence.internal.jpa.modelgen.CanonicalModelProcessor) as shown on the image below, and make sure that your Intellij IDEA project is setup with Java 1.6 (the CanonicalModelProcessor only works with 1.6). Then, you can run the process annotation (Ctrl+Alt+Maj+F9) and get the meta-classes generated.

Maven

But when it comes to continues integration, your IDE is not enough, you need you favourite building tool to do the job. And my favourite building tool is Maven (I would never have though to write favourite and Maven in the same sentence). Maven needs to invoke the annotation processor of EclipseLink to generate the meta-classes during the generate-sources goal phase with the maven-processor-plugin. So here is the pom.xml to make this work (JPA and EclipseLink dependencies + the plugin configuration) :

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

  <modelVersion>4.0.0</modelVersion>
  <groupId>org.beginningee6.demo</groupId>
  <artifactId>criteria-api</artifactId>
  <packaging>jar</packaging>
  <version>1.0</version>
  <name>JPA 2.0 Criteria API</name>

  <pluginRepositories>
    <pluginRepository>
      <id>maven-annotation-plugin</id>
      <url>http://maven-annotation-plugin.googlecode.com/svn/trunk/mavenrepo</url>
    </pluginRepository>
  </pluginRepositories>

  <dependencies>
    <dependency>
      <groupId>org.eclipse.persistence</groupId>
      <artifactId>javax.persistence</artifactId>
      <version>2.0.0</version>
    </dependency>
    <dependency>
      <groupId>org.eclipse.persistence</groupId>
      <artifactId>eclipselink</artifactId>
      <version>2.0.1</version>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <inherited>true</inherited>
        <configuration>
          <source>1.6</source>
          <target>1.6</target>
        </configuration>
      </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</compilerArguments>
              <processors>
                <processor>org.eclipse.persistence.internal.jpa.modelgen.CanonicalModelProcessor</processor>
              </processors>
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>

Model and meta-model

Let’s have a look at what this meta-model class could look like. If you have a Customer entity that looks like this :

@Entity
public class Customer {

    @Id @GeneratedValue
    private Long id;
    private String firstName;
    private String lastName;
    private Integer age;
    private String email;
    @OneToOne(fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST, CascadeType.REMOVE})
    @JoinColumn(name = "address_fk")
    private Address address;
    // Constructors, getters, setter...
}

Running the CanonicalModelProcessor will generate the following Customer_ meta-class  :

@Generated("EclipseLink-2.0.1")
@StaticMetamodel(Customer.class)
public class Customer_ { 

	public static volatile SingularAttribute id;
	public static volatile SingularAttribute firstName;
	public static volatile SingularAttribute lastName;
	public static volatile SingularAttribute age;
	public static volatile SingularAttribute email;
	public static volatile SingularAttribute address;
}

As you can see, each attribute is of type SingularAttribute (but it could have been a CollectionAttribute for attributes of type Collection). You can check all the available meta-model types in the javax.persistence.metamodel package.

Other queries

The Criteria API has all the needed methods to write complex queries. Here is a glimpse of what you can write with this new API (with or without meta-model classes) :

// Criteria builder
CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder();
CriteriaQuery<Customer> criteriaQuery;
TypedQuery<Customer> query;
Root<Customer> customer;

// select c from Customer c
criteriaQuery = criteriaBuilder.createQuery(Customer.class);
criteriaQuery.from(Customer.class);
assertEquals(ALL, em.createQuery(criteriaQuery).getResultList().size());

// select c from Customer c (setMaxResults(3))
query = em.createQuery(criteriaQuery);
query.setMaxResults(3);
assertEquals(3, query.getResultList().size());

// select c from Customer c where c.firstName = 'John'
criteriaQuery = criteriaBuilder.createQuery(Customer.class);
customer = criteriaQuery.from(Customer.class);
criteriaQuery.select(customer).where(criteriaBuilder.equal(customer.get("firstName"), "John"));
assertEquals(2, em.createQuery(criteriaQuery).getResultList().size());

// select c from Customer c where c.firstName = 'John' (using meta-model)
criteriaQuery = criteriaBuilder.createQuery(Customer.class);
customer = criteriaQuery.from(Customer.class);
criteriaQuery.select(customer).where(criteriaBuilder.equal(customer.get(Customer_.firstName), "John"));
assertEquals(2, em.createQuery(criteriaQuery).getResultList().size());

// select c from Customer c where c.firstName = 'John' (using a predicate)
criteriaQuery = criteriaBuilder.createQuery(Customer.class);
customer = criteriaQuery.from(Customer.class);
Predicate predicate = criteriaBuilder.equal(customer.get("firstName"), "John");
criteriaQuery.select(customer).where(predicate);
assertEquals(2, em.createQuery(criteriaQuery).getResultList().size());

// select c from Customer c where c.address.country = 'AU'
criteriaQuery = criteriaBuilder.createQuery(Customer.class);
customer = criteriaQuery.from(Customer.class);
criteriaQuery.select(customer).where(criteriaBuilder.equal(customer.get("address").get("country"), "AU"));
assertEquals(2, em.createQuery(criteriaQuery).getResultList().size());

// select c from Customer c where c.address.country = 'AU' (using meta-model)
criteriaQuery = criteriaBuilder.createQuery(Customer.class);
customer = criteriaQuery.from(Customer.class);
criteriaQuery.select(customer).where(criteriaBuilder.equal(customer.get(Customer_.address).get(Address_.country), "AU"));
assertEquals(2, em.createQuery(criteriaQuery).getResultList().size());

// select c from Customer c where c.age > 40
criteriaQuery = criteriaBuilder.createQuery(Customer.class);
customer = criteriaQuery.from(Customer.class);
criteriaQuery.select(customer).where(criteriaBuilder.greaterThan(customer.get("age").as(Integer.class), 40));
assertEquals(4, em.createQuery(criteriaQuery).getResultList().size());

// select c from Customer c where c.age > 40 (using meta-model)
criteriaQuery = criteriaBuilder.createQuery(Customer.class);
customer = criteriaQuery.from(Customer.class);
criteriaQuery.select(customer).where(criteriaBuilder.greaterThan(customer.get(Customer_.age), 40));
assertEquals(4, em.createQuery(criteriaQuery).getResultList().size());

// select c from Customer c where c.age between 40 and 50
criteriaQuery = criteriaBuilder.createQuery(Customer.class);
customer = criteriaQuery.from(Customer.class);
criteriaQuery.select(customer).where(criteriaBuilder.between(customer.get("age").as(Integer.class), 40, 50));
assertEquals(2, em.createQuery(criteriaQuery).getResultList().size());

// select c from Customer c where c.age between 40 and 50 (using meta-model)
criteriaQuery = criteriaBuilder.createQuery(Customer.class);
customer = criteriaQuery.from(Customer.class);
criteriaQuery.select(customer).where(criteriaBuilder.between(customer.get(Customer_.age), 40, 50));
assertEquals(2, em.createQuery(criteriaQuery).getResultList().size());

References

static metamodel class
About these ads
Comments
4 Responses to “JPA 2.0 Criteria API with Maven and EclipseLink”
  1. Nice post. If you find that JPA 2 Criteria queries are too verbose, then give Querydsl a try : http://source.mysema.com/display/querydsl/Querydsl

    Here is an example :

    // select c from Customer c where c.age between 40 and 50 (using meta-model)

    QCustomer customer = QCustomer.customer;
    JPAQuery query = new JPQuery(em);
    List customers = query.from(customer).where(customer.age.between(40,50)).list)customer);

  2. Tbee says:

    The Maven goal hangs in my project.

    But I must say, this CriteriaBuilder thing is not very readable, so maybe Querydsl is worth a second look (I was waiting for the official spec to decide what to use).

  3. Jorge says:

    Thanks for this post!!,

    these instructions work with a normal Eclipse just by installing EclipseLink?? Or I must work with Intellij IDEA?

  4. Pekka Kleimert says:

    Thanxs for a helpful post. I really liked the version without metadata and date search. I think the metadata stuff is too complicated. //Pekka

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 7,480 other followers

%d bloggers like this: