Java Persistence 2.0 Public Draft: Criteria API

Java Persistence 2.0 Public Draft (Part II) Probably the most significant of the new features introduced in the JPA 2.0 Public Draft is the Criteria API.  This is a non-string-based API for the dynamic construction of object-based queries.

Criteria queries are constructed in terms of query definition objects.  As with JPQL static and dynamic queries, criteria query definition objects are passed to the EntityManager createQuery method to create Query objects—and then executed using the methods of the Query API.

Loosely speaking, a QueryDefinition object can be thought of as a set of nodes corresponding to the semantic constructs of the query:
  • domain objects, which correspond to the range variables and other identification variables of the JPQL FROM clause
  • where clause predicates, which comprise one or more conditional expression objects
  • select clauses, which comprise one or more "select item" objects
  • order-by and group-by items
  • subqueries
  • and so on...

Constructing a Query Definition

The QueryBuilder interface is the factory for QueryDefinition objects.  A QueryBuilder instance is obtained from either the EntityManager or the EntityManagerFactory.  For example,

EntityManager em = ... ; 
QueryBuilder queryBuilder = em.getQueryBuilder();
QueryDefinition qdef = queryBuilder.createQueryDefinition();

Using the query builder, you can create either an "empty" QueryDefinition object or a QueryDefinition object with a root entity class.  The specification of query roots is the first step in constructing a query definition.  Query roots correspond to the range variables of SQL.  They specify the domain objects on which the query is based and which are not reachable by navigation or join. Query root are instances of the DomainObject interface.  In the simple case a query definition has a single root, so the QueryBuilder interface has a convenience method to support this.

DomainObject customer = queryBuilder.createQueryDefinition(Customer.class);

This method returns the DomainObject instance corresponding to the root.  It is semantically equivalent to the following in which the addRoot method is used to add a query root to an empty QueryDefinition object:

QueryDefinition qdef = queryBuilder.createQueryDefinition();
DomainObject customer = qdef.addRoot(Customer.class);

The DomainObject interface extends the QueryDefinition interface, and QueryDefinition operations applied to domain objects operate on the query definition as a whole.  Thus, for example, if you invoke the where method on a domain object, the query definition is modified to include the specified restriction predicate.

A query with a single root entity is assumed to select entities of that type, unless the select method of the QueryDefinition interface is used to specify otherwise.  Thus, the DomainObject customer in the query above represents a complete query definition.  We can pass it to the createQuery method and execute the resulting query, causing all instances of the Customer class to be returned:

Query q = em.createQuery(customer);
List myCustomers = q.getResultList();

The addition of further query roots, like additional range variables in JPQL, has the effect of inducing a cartesian product.

Given one or more query roots, a query domain can be further refined by the addition of other domain objects through join operations.   The argument to the join method is the name of the attribute in the referencing class.  The result is the added DomainObject instance.  This, like the domain objects that are roots, again references the underlying query definition object.

For example, to modify the above query definition to operate over customers and their orders, we would add:

DomainObject order = customer.join("orders");

When executed, this query returns all customers that have orders.  Note further that because the variables order and customer are referencing the same underlying query definition, the following calls to the EntityManager createQuery method are semantically equivalent:

em.createQuery(customer);
em.createQuery(order);


Defining the SELECT and WHERE Clauses

Assume we have the following classes:

@Entity public class Customer {
  @Id int custId;
  String name;
  ...
  @OneToMany(mappedBy="customer") Set<Order> orders;
  ...
}

@Entity public class Order {
  @Id int orderId;
  ...
  @ManyToOne Customer customer;
  @OneToMany(mappedBy="order") Set<LineItem> items;
  ...
}

@Entity public class LineItem {
  @Id int id;
  @ManyToOne Order order;
  @ManyToOne Product product;
  ...
}

@Entity public class Product {
  @Id int productId;
  String name;
  String productType;
  ...
}

Using JPQL, a query to return all customers ordering products of type 'printer' would look something like this:

SELECT c.name
FROM Customer c JOIN c.orders o JOIN o.items i
WHERE i.product.productType = 'printer'

To construct a semantically equivalent query using the Criteria API, we proceed as follows:

1)  Create a query definition object.  This query is rooted in the Customer class, so we again write:

DomainObject customer = queryBuilder.createQueryDefinition(Customer.class);

2)  Join Customer to Order and Order to LineItem.  Since the result of the join method is also a DomainObject, we chain the method invocations:

DomainObject item = customer.join("orders").join("items");

3)  Define the select list of the query and the restrictions over the results.  These modify the underlying query definition object, and can thus be defined in either order.  Let's assume we're defining the restrictions first:

customer.where(item.get("productType").equal("printer"));

The argument to the where method is a conditional predicate.  In this example, the arguments to the condition are a PathExpression instance (derived from invoking get("productType") on the item DomainObject) and the string "printer".  The where method returns this, so it supports method chaining.

The select method of the QueryDefinition interface modifies the query definition to specify the query result.  It also returns this:

customer.select(customer.get("name"));

Putting it all together, and using method chaining we have:

DomainObject customer = queryBuilder.createQueryDefinition(Customer.class);
DomainObject item = customer.join("orders").join("items");
customer.where(item.get("productType").equal("printer"))
        .select(customer.get("name"));

While this example of course just illustrates the basics, the Criteria API provides support for all the functionality of JPQL.

In the following sections we examine how we might rewrite the JPQL examples in part I of this blog.  I'm just going to reproduce the JPQL queries here.  To see the entity classes, please refer to part I.


Navigation

Using JPQL:

SELECT DISTINCT p.billedTo
FROM Employee e JOIN e.contactInfo.phones p
WHERE e.contactInfo.address.zipcode = '95054'
    AND p.phonetype = PhoneType.OFFICE

Or, equivalently:

SELECT DISTINCT p.billedTo
FROM Employee e JOIN e.contactInfo c JOIN c.phones p
WHERE e.contactInfo.address.zipcode = '95054'
    AND p.phonetype = PhoneType.OFFICE

This query navigates through the contactInfo embeddable in the FROM clause and again in the WHERE clause.  Because navigation in the FROM clause has the semantics of inner join, the Criteria API requires the use of the join method to construct the query domain.  The where method uses a compound predicate.

DomainObject e = queryBuilder.createQueryDefinition(Employee.class);
DomainObject p = e.join("contactInfo").join("phones");
e.where(e.get("contactInfo").get("address").get("zipcode").equal("95054")
        .and(p.get("phonetype").equal(PhoneType.OFFICE)))
 .selectDistinct(p.get("billedTo"));


Maps

Here's the first query over maps:

SELECT p
FROM PictureCategory c JOIN c.photos p
WHERE c.name = 'birds' AND KEY(p) LIKE '%egret%'

DomainObject c = queryBuilder.createQueryDefinition(PictureCategory.class);
DomainObject p = c.join("photos");
p.where(c.get("name").equal("birds").and(p.key().like("%egret%")))
 .select(p);

Note that, as in the JPQL query, a variable referring to a map type corresponds to the map value.

Here are the next two queries:

SELECT v.location.street
FROM VideoStore v JOIN v.videoInventory i
WHERE v.location.zipcode = '95054' AND KEY(i).title = 'Vertigo' AND VALUE(i) > 0

and

SELECT v.location.street, ENTRY(i)
FROM VideoStore v JOIN v.videoInventory i
WHERE v.location.zipcode = '95054' AND KEY(i).director = 'Hitchcock' AND VALUE(i) > 0

Using the Criteria API, these queries are written as follows:

DomainObject v = queryBuilder.createQueryDefinition(VideoStore.class);
DomainObject i = v.join("videoInventory");
v.where(v.get("location").get("zipcode").equal("95054")
         .and(i.key().get("title").equal("Vertigo"))
         .and(i.value().greaterThan(0)))
 .select(v.get("location").get("street"));

and

DomainObject v = queryBuilder.createQueryDefinition(VideoStore.class);
DomainObject i = v.join("videoInventory");
v.where(v.get("location").get("zipcode").equal("95054")
         .and(i.key().get("director").equal("Hitchcock"))
         .and(i.value().greaterThan(0)))
  .select(v.get("location").get("street"), i.entry());
 

Ordered Lists

Here's the query over ordered lists:

SELECT e
FROM Employee e JOIN e.dept d
WHERE d.name='Marketing' AND INDEX(e) < 5


DomainObject e = queryBuilder.createQueryDefinition(Employee.class);
DomainObject d = e.join("dept");
e.where(d.get("name").equal("Marketing").and(e.index().lessThan(5)));


Non-polymorphic Queries

The following two examples illustrate non-polymorphic queries:

Example 1:

SELECT e
FROM Employee e JOIN e.dept d
WHERE d.name = 'Marketing' AND TYPE(e) IN (PartTimeEmployee, Contractor)

In the criteria query, entity class objects are used to specify entity type arguments to the in method:

DomainObject e = queryBuilder.createQueryDefinition(Employee.class);
DomainObject d = e.join("dept");
e.where(d.get("name").equal("Marketing")
           .and(e.type().in(PartTimeEmployee.class, Contractor.class)));


Example 2:

SELECT e
FROM Employee e
WHERE TYPE(e) IN :empTypes

In the criteria query below, the param method of the QueryDefinition interface defines the parameter name.  As with JPQL, this parameter must be bound to a collection of entity class objects for the query execution.  Note that in a criteria query the parameter name is not prefixed with a colon (:).

DomainObject e = queryBuilder.createQueryDefinition(Employee.class);
e.where(e.type().in(e.param("empTypes")));

Example 3:

This query uses a TYPE expression to return the entity class:

SELECT TYPE(e), e.name
FROM Employee e JOIN e.dept d
WHERE d.name='Marketing'

As in the JPQL query, the entity class object is returned in the result of the criteria query:

DomainObject e = queryBuilder.createQueryDefinition(Employee.class);
DomainObject d = e.join("dept");
e.where(d.get("name").equal("Marketing"))
 .select(e.type(), e.get("name"));


Case Expressions

These are the case expression examples.  The Criteria API does not currently support update and delete operations.

SELECT c, CASE WHEN c.annualSpending > 10000 THEN 'Premier'
               WHEN c.annualSpending >  5000 THEN 'Gold'
               WHEN c.annualSpending >  2000 THEN 'Silver'
               ELSE 'Bronze'
          END
FROM Customer c


DomainObject c = queryBuilder.createQueryDefinition(Customer.class);
c.select(c, c.case().when(c.get("annualSpending").greaterThan(10000))
                    .then("Premier")
                    .when(c.get("annualSpending").greaterThan(5000))
                    .then("Gold")
                    .when(c.get("annualSpending").greaterThan(2000))
                    .then("Silver")
                    .else("Bronze"));

This one uses a NULLIF expression:

SELECT AVG(NULLIF(e.salary, -99999))
FROM Employee e

DomainObject e = queryBuilder.createQueryDefinition(Employee.class);
e.select(e.avg(e.nullif(e.get("salary"), -999999)));


Subqueries

The criteria API supports the use of both correlated and non-correlated subqueries, as does JPQL.   Like a top-level query, a subquery is constructed through the creation and modification of a QueryDefinition object.

The following JPQL query returns those customers whose unpaid balance is less than half of the average:

SELECT goodCustomer
FROM Customer goodCustomer
WHERE goodCustomer.balanceOwed < (
           SELECT AVG(c.balanceOwed)/2.0 FROM Customer c)

Using the criteria API, the corresponding query can be written as shown below.  There are QueryDefinition objects for both the subquery and the containing query.

DomainObject goodCustomer = queryBuilder.createQueryDefinition(Customer.class);
DomainObject customer = queryBuilder.createQueryDefinition(Customer.class);
goodCustomer.where(goodCustomer.get("balanceOwed)
                     .lessThan(customer.select(customer.get("balancedOwed")
                                                       .dividedBy(2.0)
                                                       .avg())));


In a correlated subquery, the subquery and the containing query share variables.

The following JPQL query selects those employees that make more than all of the managers in their department:

SELECT emp
FROM Employee emp
WHERE emp.salary > ALL (
    SELECT m.salary
    FROM Manager m
    WHERE m.department = emp.department)


Here's the criteria API equivalent.  I'm using QueryDefinition variables to make this very explicit, although like the earlier queries, this query can be written without them.

QueryDefinition qOuter = queryBuilder.createQueryDefinition();
DomainObject emp = qOuter.addRoot(Employee.class);

QueryDefinition qSubq = queryBuilder.createQueryDefinition();
DomainObject mgr = qSubq.addRoot(Manager.class);

qSubq.select(mgr.get("salary"))
     .where(mgr.get("department").equal(emp.get("department")));

qOuter.where(emp.get("salary").greaterThan(qSubq.all()));


Current Status


Our work in the Expert Group in designing the Criteria API involved numerous iterations and considerable debate as to the best approach, and we welcome your feedback on the results.  Not yet reflected in the draft is our intention to provide additional and/or alternative ways to obtain QueryBuilder objects to support the construction of QueryDefinition objects without the need for an entity manager or entity manager factory in order to support disconnected usage scenarios.  Since the provider's implementation of the QueryDefinition object is required to be serializable, this would also allow for QueryDefinition objects to be passed from a remote tier.

When you download the draft and review the spec, you'll notice that it flags a number of open issues and items that are in need of developer feedback.   Please feel free to send me and the expert group your thoughts at  jsr-317-pdr-feedback@sun.com.    Thanks!



Comments:

Linda,

Excellent blog. Do you think that support for disconnected QueryDefinition objects will make JPA 2.0 or do you think this will be JPA.next?

Regards
Lance

Posted by Lance Andersen on November 25, 2008 at 11:08 PM PST #

Thanks, Lance. Our plans are to try to add this support in this release.

Posted by Linda DeMichiel on November 26, 2008 at 07:52 AM PST #

Nice work. I am particularly happy to see that disconnected QueryDefinition objects will be supported.

Ismael

Posted by Ismael Juma on November 29, 2008 at 12:33 AM PST #

[Trackback] Good Lord, i�m happy to see this

Posted by Confluence: codesmell on November 29, 2008 at 02:10 AM PST #

[Trackback] Good Lord, i�m happy to see this

Posted by Confluence: codesmell on November 29, 2008 at 02:11 AM PST #

+1 for building QueryDefinition in client side. Great jobs!

Alexandre Garino

Posted by Alexandre Garino on November 30, 2008 at 12:49 AM PST #

Nice, this is the reason I am still on the hibernate session camp. I am a BIG fan of criteria queries and I am very happy to see them getting standardized.

Posted by Marcelo Morales on December 01, 2008 at 08:40 PM PST #

I guess I must be missing something but isn't this a string? - "orders"

Posted by Mark on December 01, 2008 at 11:08 PM PST #

finally! looks great!..... just a question, will there be a more type-safe version of the criteria API? Just read about LiquidForm ond http://www.theserverside.com/news/thread.tss?thread_id=52054. A type safe library for cretaing JPA queries.

Posted by kristian marinkovic on December 02, 2008 at 12:08 AM PST #

Excellent work!

Posted by Pablo on December 04, 2008 at 07:51 PM PST #

Very important issue !! My congratulations to all of you, it is a very important improvement !! ... but ... FlushMode=MANUAL is missing from new spec too !!

Posted by Maximiliano on December 15, 2008 at 08:50 AM PST #

Exactly. What happened to FlushMode=MANUAL? Mike, you were going to write a blog entry about why this approach was broken?

Posted by Manuel on December 16, 2008 at 12:06 AM PST #

For a more typesafe way to use Hibernate you might also want to check out Querydsl : http://source.mysema.com/display/querydsl/Querydsl

JPA 2 :
DomainObject customer = queryBuilder.createQueryDefinition(Customer.class);
DomainObject item = customer.join("orders").join("items");
customer.where(item.get("productType").equal("printer"))
.select(customer.get("name"));

Querydsl :
Query q = new HqlQuery(session); // or new JpaqlQuery(em);
List<String> customerNames = q.from(customer).innerJoin(customer.orders.as(order))
.innerJoin(order.items.as(item))
.where(item.productType.eq("printer")).list(customer.name);

Posted by Timo Westkämper on December 17, 2008 at 06:30 PM PST #

Hi,
Congrats on the improvements on the API. However, I'm still worried about the string literals one needs to use, as that implies errors that won't be detected at compile time.
I'm talking about the get("field_name") and join.("fk_name") that are still written as strings.

LiquidForm solves that in a smart way, using CGLIB proxies to get the compile-safe methods to return the strings, and that means fields are verified at compile time, you can use autocomplete in IDEs.. You can see it here: http://code.google.com/p/liquidform/
Would that be possible to be added?

Cheers!
PD: Just to clarify, I'm not related in anyway to that project, but if we are going to get rid of unsafe strings, let's get rid of the them completely. Else there's no point to complicate the code to still find runtime errors even when the descriptors are correct.

Posted by Daniel Lopez on December 17, 2008 at 10:06 PM PST #

A number of folks have commented on the typesafety issue, which I'm also very concerned by.

I've proposed a solution to the EG, described here:

http://in.relation.to/Bloggers/ATypesafeCriteriaQueryAPIForJPA
http://in.relation.to/Bloggers/Java6CompilerPluginsAndTypesafeCriteriaQueries

Take a look, and tell us what you think.

Posted by Gavin King on December 21, 2008 at 10:34 AM PST #

I had not thought about the problems with requiring getters/setters, good to know. I like your second proposal, not only the complete type safety but also the syntax, using the SQL idioms for the method names (select, where...) makes it more natural to read for those trained to read SQL.
I'm not in the "against code generation" camp, so it looks pretty good to me, as far as Java is able to go now. Even though writing queries that way is more verbose, I think the benefits one can get from it make it a worth addition to the bag of tools. It can come specially handy for building dynamic queries, where string concatenation to prevent dummy queries can become quite a nightmare.

Posted by Daniel Lopez on December 21, 2008 at 09:34 PM PST #

Great work on the Criteria API.

Count me in as one who likes the typesafe model Gavin is proposing.

I especially like the whole idea of a JPA metamodel in general as a way to problematically reflect upon the Entity Model. I would love in JPA, for example, to determine the PK of an entity without having to do my own orm.xml parsing and annotation reading. I believe a meta model would be a great boon for JPA addon libraries and frameworks. I think Gavin's typesafe criteria API is simply one excellent example of what could be done with a JPA metamodel.

Posted by Mike Youngstrom on December 22, 2008 at 06:15 AM PST #

Hi,

DomainObject p = e.join("contactInfo").join("phones");

Why join is needed? We are not working with tables, but objects. Joins?
The needed joins can be deduced from condition.

e.where(e.get("contactInfo").get("address").get("zipcode").equal("95054")
.and(p.get("phonetype").equal(PhoneType.OFFICE)))
.selectDistinct(p.get("billedTo"));

It's really ugly (at least for me) and not very readable. Also it's not typesafe, you are using strings. Where is the advantage?

Posted by Javier Paniza on December 23, 2008 at 07:24 PM PST #

> The Criteria API does not currently support update and delete operations.

Linda, will the final release of JSR317 provide these features?

Frank

Posted by Frank Schwarz on January 10, 2009 at 04:21 AM PST #

Is there any news about Flushmode in Manual ?

Posted by Maximiliano Carrizo on May 17, 2009 at 02:53 AM PDT #

As a consumer of a domain model, I should not need to know that another object must be "joined" to the object on which I'm basing my query criteria.

In other words, I should be able to say customer.get("items").get("product"), navigating those fields like any others; if a "join" (a relational database term) needs to happen, it should be the framework that realizes this based on my calls to .get and does whatever is necessary to ensure that the query works.

Posted by Nick Johnson on May 26, 2009 at 01:15 AM PDT #

You're getting hung up on words. You see the word "join", and think it must mean something about relational databases because the word is also a keyword in SQL. Instead, you should try to actually understand the real semantic difference between "join" and "get" in the query language, which has nothing to do with the SQL. Hint: they \*both\* (usually) result in relational database joins.

Posted by Gavin King on May 26, 2009 at 01:28 AM PDT #

That's not the point. Why should I need to know whether something needs to be joined to something else, relational or otherwise? The ORM knows when a join is required, so why should I, as a consumer of a domain model, need to know?

Why can't I just navigate the domain model naturally, like I would if it weren't even attached to relational storage, or if I were looking at any other fields of the same object (versus fields that happen to 'join' to something else)?

The Toplink guys got this right eons ago with their ExpressionBuilder and Expression classes. You don't have to tell it when you need a "join" because it already knows... and you shouldn't have to.

Posted by Nick Johnson on June 11, 2009 at 02:30 AM PDT #

Hi, Is there a way to create "SELECT COUNT() from.." with criteria api? searching the web I found some request to add such call to hibernate, but I can't fin it neither in jpa 2.0 specification nor in current jps 2.0 reference implementation. May be I just miss it. Thanks.

Posted by Sergey on August 12, 2009 at 11:42 PM PDT #

Hi Linda,

Coming back to the disconnected query topic again.

Our use case is to be able to create the query on the client and then pass this to the server into a dao, which then runs the query.

We would also like to be able to persist the query and retrieve it later.

Can the criteria be inspected so that a query builder form can then be presented to the user from the saved query.

Just a few topics.

Posted by Darren on August 20, 2009 at 09:09 PM PDT #

Whew! This is going to be a much needed addition to JPA. Thanks a lot.

Posted by Anjan Banerjee on October 17, 2009 at 01:42 AM PDT #

The implementation of the Criteria API is already available ?

how can I configure Maven 2 to use the JPA 2.0 with Criteria ?

Posted by Felipe Gaúcho on November 29, 2009 at 05:07 PM PST #

I'm using Hibernate 3.5.0-CR-2 at the moment for testing JPA 2 and Criteria API.

Perhaps I missed it in the explanation, but...
Will Criteria Example queries be possible with JPA 2.0 Criteria API?

Like the kind of:
Person person = new Person();
person.setName("Duke");
Criteria criteria = ... // create Criteria
criteria.add(Example.create(person).ignoreCase());

Will this be available?

Posted by Steve Schols on March 30, 2010 at 05:31 AM PDT #

Post a Comment:
  • HTML Syntax: NOT allowed
About

Linda DeMichiel is the Specification Lead for Java EE 7 and for Java Persistence 2.1, and a member of the Java Platform, Enterprise Edition team at Oracle. She was formerly the Specification Lead for JPA 2.0 and for EJB 3.0.

Search

Top Tags
Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today