Since its introduction some two years ago, the Java Persistence API (JPA), originally part of the EJB 3.0 specification, has quickly become a popular mechanism to persist Java objects in relational databases. A key component of the JPA is its query language, JPQL.
While the JPQL significantly simplified working with persistent Java objects—for instance, by providing easy object navigation—JPQL queries are specified as Strings. Thus, while the queries themselves operate over strongly-typed Java objects, the query language relies on String-based expressions. Although some IDEs provide intelligent inspection of JPQL query expressions, JPQL still can't take advantage of many type-inspired coding features, such as robust refactoring.
Some O/R mapping frameworks, such as Hibernate, pioneered the use type-safe, criteria-based query APIs; in a recent blog post, Java Persistence 2.0 Public Draft: Criteria API, JSR 317 spec lead Linda DeMichiel explains how a similar criteria API is now part of the upcoming JPA 2.0 specification.
According to DeMichiel, the new criteria API is a "non-string-based API for the dynamic construction of object-based queries:"
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
DeMichiel explains that constructing queries with the new API starts similarly to how queries are currently built using JPQL, but then the two APIs diverge. An interesting point DeMichiel's examples highlight is the fluent interface of the Criteria API:
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...
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.
What do you think of the new Criteria API in JPA 2.0?
After playing around with Mockito (http://code.google.com/p/mockito/), I think we can improve the type safety further by exchanging the DomainObject instances with proxies to the actual objects. Here's are some of the examples from the Public Draft by using dynamic proxies.
These examples compile and can, at least for these simple examples, organize the information correctly. What do you think, could this be an interesting addition to the proposal?
queryBuilder = new QueryBuilder();
Customer customer = queryBuilder.createQueryDefinition(Customer.class);
// NB: Not entirely pleased with this hack with collections
Item item = customer.getOrders().get(0).getItems().get(0);
queryBuilder.where(item.getProductType()).eq("printer")
.select(customer.getName(), customer.getAddress());
queryBuilder = new QueryBuilder();
Employee employee = queryBuilder.createQueryDefinition(Employee.class);
queryBuilder.where(employee.getContactInfo().getAddress().getZipCode()).eq("95054")
.where(employee.getContactInfo().getPhones().get(0).getPhoneType()).eq(PhoneType.OFFICE)
.selectDistinct(employee.getContactInfo().getPhones().get(0).getBilledTo());
// Case syntax
queryBuilder = new QueryBuilder();
customer = queryBuilder.createQueryDefinition(Customer.class);
queryBuilder.caseExp()
// Please note: The compiler would stop us if we said e.g. .gt("ten thousand")
.when(customer.getAnnualSpending()).gt(10000.0).then("Premier")
.when(customer.getAnnualSpending()).gt(5000.0).then("Gold")
.when(customer.getAnnualSpending()).gt(200.0).then("Silver")
.otherwise("Bronze");
// Compressed case syntax
queryBuilder = new QueryBuilder();
customer = queryBuilder.createQueryDefinition(Customer.class);
queryBuilder.caseExp(customer.getAnnualSpending())
// Please note: The compiler stops us from saying e.g. gt("ten thousand")
.when().gt(10000.0).then("Premier")
.when().gt(5000.0).then("Gold")
.when().gt(200.0).then("Silver")
.otherwise("Bronze");
LINQ also started that way, the idea - as far as I know - is that if you like the API and enough people clame for the feature it can become a Java LINQ.