Hibernate Criteria API: Multi-Criteria Search Made Easy

ulti-criteria search functionalities, which allow combinations of many optional criteria fields, are common in modern Web-enabled business applications (see Sidebar 1. When to Use Multi-Criteria Searches). An example of this functionality is an online accommodations database (see Figure 1) that allows users to search accommodations by country, availability, type (hotel, bed and breakfast, etc.), and capacity. All the criteria are optional, and the user may use any combination of criteria.

Click to enlarge
Figure 1: A Multi-Criteria Search Screen

The traditional approach for a Hibernate developer to enable this sort of functionality generally involves building an HQL (Hibernate Query Language) query on the fly, based on the search criteria the user entered. Next, he or she uses this query string to create a Hibernate Query object and then sets the non-null parameter values. The code looks something like this:

...if (startDate != null) {    queryBuf.append(firstClause ? " where ": " and ");    queryBuf.append("a.availabiliyDate >=:startDate");    parameters.put("startDate",startDate);    firstClause = false;}  ...  

(See Listing 1 for a full example. Note that the model has been simplified for the sake of readability. Accommodation availability data would normally be stored in a separate table, of course.)

Listing 1. A Traditional Approach for Building Multi-Criteria HQL Queries
public List searchAccommodation(Date startDate,                               Date endDate,                               Country country,                               AccommodationType type,                               Integer capacity)    Map parameters = new HashMap();    boolean firstClause = true;    StringBuffer queryBuf            = new StringBuffer("from Accommodation a ");    if (startDate != null) {        queryBuf.append(firstClause ? " where " : " and ");        queryBuf.append("a.availabiliyDate >= :startDate");        parameters.put("startDate",startDate);        firstClause = false;    }        if (endDate != null) {        queryBuf.append(firstClause ? " where " : " and ");        queryBuf.append("a.availabiliyDate <= :endDate");        parameters.put("endDate",endDate);        firstClause = false;    }    if (country != null) {        queryBuf.append(firstClause ? " where " : " and ");        queryBuf.append("a.country = :country");    }    if (type != null) {        queryBuf.append(firstClause ? " where " : " and ");        queryBuf.append("a.type = :type");    }    if (maxCapacity != null) {        queryBuf.append(firstClause ? " where " : " and ");        queryBuf.append("a.capacity >= :capacity");    }    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    //    return query.list();}

This approach is cumbersome and error-prone. It is also risky in a team-development context, as inexperienced developers 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 += " a.availabilityDate >= '"              + startDate + "'";} // And so on...               

So, is there better way? Yes, there is: the Hibernate criteria API, a powerful and elegant technique for complex, dynamic search functionalities. Using the online accommodations database cited previously, this article examines the criteria API solution and its benefits.

Introducing the Hibernate Query API

The Hibernate Query API lets you write the previous 36-line example in Listing 1 using just 17 lines of quite readable code (see Listing 2 for the full example):

...Criteria criteria             = session.createCriteria(Accommodation.class);if (startDate != null) {    criteria.add(Expression.ge("availabilityDate",                               startDate);}                if (endDate != null) {    criteria.add(Expression.le("availabilityDate",                               endDate);}                // and so on

Listing 2. Using the Hibernate Criteria API for Multi-Criteria Queries
public List searchAccommodation(Date startDate,                               Date endDate,                               Country country,                               AccommodationType type,                               Integer capacity)    Criteria criteria             = session.createCriteria(Accommodation.class);    if (startDate != null) {        criteria.add(Expression.ge("availabilityDate",                                   startDate);    }                    if (endDate != null) {        criteria.add(Expression.le("availabilityDate",                               endDate);    }                    if (country != null) {          criteria.add(Expression.eq("country",country);    }                    if (capacity != null) {          criteria.add(Expression.ge("capacity",capacity);    }                    if (type != null) {          criteria.add(Expression.eq("type",type);    }                    List results = criteria.list();    //    // Execute the query    //    return query.list();}

Let’s take a closer look at how you create and use the Criteria object. The principal class in the API is the Criteria class. You create a Criteria object using the createCriteria() method in the Hibernate session object:

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

Once created, you build the query by adding Criterion objects, obtained from the Expression class:

criteria.add(Expression.ge("availabilityDate",                           startDate));criteria.add(Expression.le("availabilityDate",                           endDate));

You can also add ‘order by’ clauses:

criteria.addOrder( Order.asc("availabilityDate") )

You may use methods such as setFirstResult(), setMaxResults(), and setCacheable() to customize the query behavior in the same way as in the Query interface. So, to get the first 10 results, use the following commands:

criteria.setFirstResult(0)criteria.setMaxResults(10)

Finally, to execute the query, invoke the list() method (or, if appropriate, the uniqueResult() method). Here is the full example:

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

Expressions

The Hibernate Query API supports a rich set of comparison operators. The standard SQL operators (=, <, ?, >, ? ) are supported by the following methods in the Expression class, respectively: eq(), lt(), le(), gt(), ge(). The following example uses ge() and eq():

session.createCriteria(Accommodation.class)              .add(Expression.ge("availabilityDate",                                 startDate)              .list();     session.createCriteria(Accommodation.class)              .add(Expression.eq("country",country))              .list();

The API also provides additional comparison operators: like, between, in, isNull, isNotNull, etc. The following example uses between, in, and like:

session.createCriteria(Accommodation.class)       .add(Expression.between("availabilityDate",                                startDate,                                endDate))       .list(); session.createCriteria(Country.class)       .add(Expression.like("A%"))       .list();   favoriteOwners = new ArrayList();...session.createCriteria(Accommodation.class)      .add(Expression.in("owner",                         favoriteOwners))      .list();

Finally, if you (really) need to, you can also build nested expressions using ‘and’ and ‘or’, as in the following example:

session.createCriteria(Accommodation.class)       .add(Expression.or(              Expression.eq("owner",peter),              Expression.eq("owner",paul)           )       .list();

In practice, I have found little use for ‘and’ and ‘or’ clauses in true dynamic queries, but who knows? You might think of one!

Ordering Results

To order your query results, you use the addOrder() method and the Order class:

session.createCriteria(Accommodation.class)       .add(Expression.between("availabilityDate",                                startDate,                                endDate))       .addOrder( Order.desc("availabilityDate") )       .addOrder( Order.asc("country.name") )       .list();

The generated HQL query would be something like:

from Accommodation a order by a.availabilityDate desc, country.name asc

Joining Tables

In HQL, additional associated objects can be loaded in the same query using a “left join fetch” clause (see Sidebar 2. Hibernate Joins), as in the following example:

from Accommodation awhere a.availabilityDate >=:startDateleft join fetch a.country

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

session.createCriteria(Accommodation.class)       .setFetchMode("country",FetchMode.EAGER)       .list();

Sometimes you don’t need to actually load the associated object (or set of objects). You just need to use one of its columns as a query criterion. Say you now need to add a language criterion in the search screen. For example, the user may want to find all accommodations where French is one of the languages spoken. To model this relation, you attach a set of languages to the Accommodation object. In HQL, the query might be the following:

from Accommodation ajoin a.languages languagewhere language.code = 'FR'

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

session.createCriteria(Accommodation.class)       .createCriteria("languages", "language")       .add(Expression.eq("language.code", "FR"))       .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(Accommodation.class)       .createAlias("languages", "language")       .add(Expression.eq("language.code", "FR"))       .list();

Note that in both these cases, the languages collection in each Accommodation object will not be initialized?it is simply used as part of the search criteria.

QBE: Query by Example

Another interesting approach is QBE. Basically, you instantiate one of your business objects with the corresponding values coming from the search screen. Hibernate then builds the corresponding query using the non-null field values:

    Accommodation accommodationEx = new Accommodation();    accommodationEx.setCountry(country);    accommodationEx.setCapacity(capacity);	 // and so on    Example example                 = Example.create(accommodationEx)                         .ignoreCase()                         .excludeZeroes()                         .excludeProperty("doNotUse")                          .enableLike(MatchMode.ANYWHERE);    return getSession()              .createCriteria(Accommodation.class)              .add(example)              .add(Expression.between("availabilityDate",                                       startDate,                                       endDate))                                      .list();

You can also fine-tune the way the QBE query is built in many ways, for example:

  • Queries may be case sensitive or insensitive (ignoreCase()).
  • Zero-valued fields may be ignored (excludeZeroes()).
  • Certain properties may be ignored (excludeProperty()).
  • And so on…

The interesting thing here is that, if you have a lot of text fields in your multi-criteria search screen, you can use QBE to initialize the text values, and then use Expression-based criterion objects to add further constraints. In fact, the Example object is just another Criterion. So you can mix and match QBE and Criteria-based querying as you need.

Powerful, Elegant and Definitely Worth a Try

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

Share the Post:
Share on facebook
Share on twitter
Share on linkedin

Overview

The Latest

homes in the real estate industry

Exploring the Latest Tech Trends Impacting the Real Estate Industry

The real estate industry is changing thanks to the newest technological advancements. These new developments — from blockchain and AI to virtual reality and 3D printing — are poised to change how we buy and sell homes. Real estate brokers, buyers, sellers, wholesale real estate professionals, fix and flippers, and beyond may

man on floor with data

DevX Quick Guide to Data Ingestion

One of the biggest trends of the 21st century is the massive surge in internet usage. With major innovations such as smart technology, social media, and online shopping sites, the internet has become an essential part of everyday life for a large portion of the population. Due to this internet

payment via phone

7 Ways Technology Has Changed Traditional Payments

In today’s digital world, technology has changed how we make payments. From contactless cards to mobile wallets, it’s now easier to pay for goods and services without carrying cash or using a checkbook. This article will look at seven of the most significant ways technology has transformed traditional payment methods.