About the Author
John Ferguson Smart is currently Project Director in a French IT firm specialised in J2EE solutions. He holds a PhD in Computer Science from the University of Aix-Marseille, France. His specialities are J2EE architecture and developpment and IT project management, including offshore project management. He works on large-scale J2EE projects for government and business with teams spread across France, Egpyt and Algeria, and writes the occasional technical article in the J2EE field.
Spotlight Features

The Rich Engineering Heritage Behind Dependency Injection

Andrew McVeigh takes us on a tour of the rich heritage behind dependency injection, what it represents, and tells us why its here to stay.

NetBeans 6: Matisse Updates

NetBeans 6 delivers great updates to the Matisse GUI builder. Spend a few minutes with Roman Strobl and get an expert briefing on what's new and what has changed.

Introduction to Groovy Part 3

In this, the third and final installation of Andres' Introduction to Groovy series, you learn about how Groovy handles variable numbers of arguments, named parameters, currying, and more about Groovy operators. Including, some new operators.

Easier Custom Components with Swing Fuse

Swing Fuse (actually just Fuse), is a framework designed to make it easier to create your own custom desktop components. In this article, Daniel Spiewak shows you how to get started and provides sample source code you can download.

Benchmark Analysis: Guice vs Spring

Willam Louth shows how he uses JXInsight Probes to investigate probable performance issues with code bases that he is not familiar with. He also highlights possible pitfalls in creating a benchmark, as well as in the analysis of results.

Hibernate Querying 102 : Criteria API

Introduction

In this article, we examine the Hibernate Criteria API, a powerful and elegent alternative to HQL well adapted for dynamic search functionalities where complex Hibernate queries have to be generated 'on-the-fly'. Be sure you catch the first article in the series, Hibernate Querying 101: Tips and Tricks, before you start this tutorial.

Multi-criteria search functionalities

Complex multi-criteria search functionalities are frequently found in web-enabled business applications. Multi-criteria search screens typically have a large number of search criteria, many of which are optional. Well-known general public multi-criteria search functionalities can be found in the 'Advanced Search' screens on sites such as Yahoo, AltaVista, and Lycos.

When are multi-criteria search screens appropriate?

In many public web sites (such as Amazon), complex multi-criteria search functionnalites tend to be less frequent these days, for the very good reason that users generally don't use them (or at least, not efficiently). EBay for example has a powerful multi-criteria search functionality which for which the business case is probably fairly limited. A good key-word and full-text search is usually preferable for sites aimed at the general public.

There are exceptions, though. Travel sites, such as air and train reservation sites, are cases where multi-criteria searches are both necessary and intuitive.

As we have said, business web sites are also another story.

The traditional approach

The traditional approach to implementing a multi-criteria search query with Hibernate involves building an HQL query on-the-fly, based on the search criteria entered by the user.

Here is an example of one such approach :

 Map parameters = new HashMap();
 StringBuffer queryBuf = new StringBuffer("from Sale s ");
 boolean firstClause = true;

 if (startDate != null) {
	  queryBuf.append(firstClause ? " where " : " and ");
	  queryBuf.append("s.date >= :startDate");
	  parameters.put("startDate",startDate);
	  firstClause = false;
 }
 if (endDate != null) {
	  queryBuf.append(firstClause ? " where " : " and ");
	  queryBuf.append("s.date <= :endDate");
	  parameters.put("endDate",endDate);
	  firstClause = false;
 }
 // And so on for all the query criteria...

 String hqlQuery = queryBuf.toString();
 Query query = session.createQuery(hqlQuery);

 //
 // Set query parameter values
 //
 Iterator iter = parameters.keySet().iterator();
 while (iter.hasNext()) {
	  String name = (String) iter.next();
	  Object value = map.get(name);
	  query.setParameter(name,value);
 }
 //
 // Execute the query
 //
 List results = query.list();

This approach is cumbersome and error-prone. It is also risky in a team-development context, as inexperienced developpers will often take dangerous short-cuts using this approach. During code review, I?ve often come across multi-criteria search functions using error-prone String concatenation, direct use of query parameters in the query string, and so on :

...
if (startDate != null) {
	if (firstClause) {
		query = query + " where ";
	} else {
		query = query + " and ";
   query += " s.date >= '" + startDate + "'";
}
// And so on...

As we will see, the Hibernate Criteria API provides a safer and cleaner solution to this type of problem.

Using the Hibernate Criteria API

The Hibernate Criteria API provides an elegant way of building on-the-fly dynamic queries on Hibernate-persisted databases. Using this technique, the previous 24-line example can be coded more consicely and more clearly using a mere 8 lines of code :

Criteria criteria = session.createCriteria(Sale.class);
if (startDate != null) {
	criteria.add(Expression.ge("date",startDate);
}
if (endDate != null) {
	criteria.add(Expression.le("date",endDate);
}
List results = criteria.list();

Let's have a look at the use of the Hibernate Criteria API in more detail.

Creating and using the Hibernate Criteria object

A Criteria object is created using the createCriteria() method in the Hibernate session object :

    Criteria criteria = session.createCriteria(Sale.class);

Once created, you add Criterion objects (generally obtained from static methods of the Expression class) to build the query. Methods such as setFirstResult(), setMaxResults(), and setCacheable() may be used to customize the query behaviour in the same way as in the Query interface. Finally, to execute the query, the list() (or, if appropriate uniqueResult()) method is invoqued :

List sales = session.createCriteria(Sale.class)
			 .add(Expression.ge("date",startDate);
			 .add(Expression.le("date",endDate);
			 .addOrder( Order.asc("date") )
			 .setFirstResult(0)
			 .setMaxResults(10)
			 .list();

Expressions

The Hibernate Criteria API supports a rich set of comparaison operators.

The standard SQL operators (=, <, ?, >, ? ) are supported respectively by the following methods in the Expression class : eq(), lt(), le(), gt(), ge() :

session.createCriteria(Sale.class)
	  .add(Expression.lt("date",salesDate))
	  .list();

session.createCriteria(Sale.class)
	  .add(Expression.eq("product",someProduct))
	  .list();

Note that, as in HQL and unlike in a JDBC-based query, you are free to use business objects as query parameters, without having to use primary and foriegn key references.

The API also provides additional comparaison operators : like, between, in, isNull, isNotNull...

session.createCriteria(Sale.class)
	  .add(Expression.between("date", startDate, endDate))
	  .list();

session.createCriteria(Product.class)
	  .add(Expression.like("A%"))
	  .list();

session.createCriteria(Product.class)
	  .add(Expression.in("color",selectedColors))
	  .list();

In addition, the API provides convenient operators which : eqProperty, ltProperty, etc?

session.createCriteria(Sale.class)
	  .eqProperty("saleDate","releaseDate")
	  .list();

Ordering results

In HQL (and SQL), the order by clause allows you to order your query results. Using the Query API, this is done using the addOrder() method and the Order class :

session.createCriteria(Sale.class)
	   .add(Expression.between("date", startDate, endDate))
	   .addOrder( Order.desc("date") )
	   .addOrder( Order.asc("product.number") )
	   .list();

Joining tables

When writing HQL queries, join clauses are often necessary to optimise the query using a "left join fetch" clause, as in the following example (I discuss this type of optimisation in another article.)

from Sale sale
where sale.date > :startDate
left join fetch sale.product

When using the criteria API, you can do the same thing using the setFetchMode() function :

session.createCriteria(Sale.class)
	   .setFetchMode("product",FetchMode.EAGER)
	   .list();

Imagine the case of an online shop which sells shirts. Each shirt model comes in a certain number of available sizes. You want a query to find all the shirt models with sizes over 40. In HQL, the query might be the following :

from Shirt shirt
join shirt.availableSizes size
where size.number > 40

Using the Criteria API, you use the createCriteria() to create an inner join between the two tables, as in the following example :

session.createCriteria(Shirt.class)
	   .createCriteria("availableSizes")
	   .add(Expression.gt("number", new Integer(40)))
	   .list();

Another way of doing this is to use the createAlias() method, which does not involve creating a new instance of the Criteria class.

session.createCriteria(Shirt.class)
	   .createAlias("availableSizes","size")
	   .add(Expression.gt("size.number", new Integer(40)))
	   .list();

Note that in both these cases, the availableSizes collection in each Shirt object will not be initialised : it is simply used as part of the search criteria.

When the Hibernate Criteria API is not appropriate

As we can see, the Hibernate Criteria API is without doubt tailor-made for dynamic query generation. It is worth noting however that there are many places where its use is not appropriate, and indeed will create code which is more complex and harder to maintain than using a standard HQL query. If the query to be executed does not involve dynamic construction (that is, the HQL query can be externalised as a named query in the Hibernate mapping files), than the use of the Hibernate Criteria API is probably not appropriate. When possible, externalising static queries presents a number of advantages :

  • Externalised queries can be audited and optimised if necessary by the DBA
  • Named queries stored in the Hibernate mapping files are easier to maintain than queries scattered through the Java code
  • Hibernate Named Queries are easy to cache if necessary

Conclusion

The Hibernate Criteria API is a powerful and elegent library which is well adapted for implementing multi-criteria search functionnalities where Hibernate queries must be built 'on-the-fly'. Using it in appropriate circumstances will result in cleaner, clearer, more reliable and more maintainable code.

Discuss this article with John