Flylib.com

Books Software

 
 
 

Chapter 8. Criteria Queries

     

Chapter 8. Criteria Queries

NOTE

In this chapter:

  • Using Simple Criteria

  • Compounding Criteria

  • Applying Criteria to Associations

  • Querying by Example

Relational query languages like HQL (and SQL, on which it's based) are extremely flexible and powerful, but they take a long time to truly master. Many application developers get by with a rudimentary understanding, cribbing similar examples from past projects, and calling in database experts when they need to come up with something truly new, or to understand a particularly cryptic query expression.

It can also be awkward to mix a query language's syntax with Java code. The section 'Better Ways to Build Queries' in Chapter 3 showed how to at least keep the queries in a separate file so they can be seen and edited in one piece, free of Java string escape sequences and concatenation syntax. Even with that technique, though, the HQL isn't parsed until the mapping document is loaded, which means that any syntax errors it might harbor won't be caught until the application is running.

Hibernate offers an unusual solution to these problems in the form of criteria queries. They provide a way to create and connect simple Java objects that act as filters for picking your desired results. You can build up nested, structured expressions. The mechanism also allows you to supply example objects to show what you're looking for, with control over which details matter and which properties to ignore.

As you'll see, this can be quite convenient . To be fair, it has its own disadvantages. Expanding long query expressions into a Java API makes them take more room, and they'll be less familiar to experienced database developers than a SQL-like query. There are also some things you simply can't express using the current criteria API, such as projection (retrieving a subset of the properties of a class, e.g., ' select title, id from com.oreilly.hh.Track ' rather than ' select * from com.oreilly. hh.Track ') and aggregation (summarizing results, e.g., getting the sum, average, or count of a property). The next chapter shows how to accomplish such tasks using Hibernate's object-oriented query language.

     

8.1 Using Simple Criteria

Let's start by building a criteria query to find tracks shorter than a specified length, replacing the HQL we used in Example 3-9 and updating the code of Example 3-10.

8.1.1 How do I do that?

The first thing we need to figure out is how to specify the kind of object we're interested in retrieving. There is no query language involved in building criteria queries. Instead, you build up a tree of Criteria objects describing what you want. The Hibernate Session acts as a factory for these criteria, and you start, conveniently enough, by specifying the type of objects you want to retrieve.

Edit QueryTest.java , replacing the contents of the tracksNoLongerThan() method with those shown in Example 8-1.

Example 8-1. The beginnings of a criteria query
public static List tracksNoLongerThan(Time length, Session session)

    throws HibernateException

{

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

    return criteria.list();

}

NOTE

These examples assume the database has been set up as described in the preceding chapters. If you don't want to go through all that, download the sample code, then jump into this chapter and run the ' codegen ', 'schema', and 'ctest' targets.

The session's createCriteria() method builds a criteria query that will return instances of the persistent class you supply as an argument. Easy enough. If you run the example at this point, of course, you'll see all the tracks in the database, since we haven't gotten around to expressing any actual criteria to limit our results yet (Example 8-2).

Example 8-2. Our fledgling criteria query returns all tracks
%

ant qtest

...

qtest:

    [java] Track: "Russian Trance" (PPK) 00:03:30, from Compact Disc

    [java] Track: "Video Killed the Radio Star" (The Buggles) 00:03:49, from VHS

Videocassette Tape

    [java] Track: "Gravity's Angel" (Laurie Anderson) 00:06:06, from Compact Disc

    [java] Track: "Adagio for Strings (Ferry Corsten Remix)" (Ferry Corsten,

William Orbit, Samuel Barber) 00:06:35, from Compact Disc

    [java] Track: "Adagio for Strings (ATB Remix)" (ATB, William Orbit, Samuel

Barber) 00:07:39, from Compact Disc

    [java] Track: "The World '99" (Ferry Corsten, Pulp Victim) 00:07:05, from

Digital Audio Stream

    [java] Track: "Test Tone 1" 00:00:10

    [java] Comment: Pink noise to test equalization

OK, easy enough. How about picking the tracks we want? Also easy! Add a new import statement at the top of the file:

import net.sf.hibernate.expression.*;

Then just add one more line to the method, as in Example 8-3.

Example 8-3. The criteria query that fully replaces the HQL version from Chapter 3
public static List tracksNoLongerThan(Time length, Session session)

    throws HibernateException

{

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

criteria.add(Expression.le("playTime", length));

return criteria.list();

}

The Expression class acts as a factory for obtaining Criterion instances that can specify different kinds of constraints on your query. Its le() method creates a criterion that constrains a property to be less than or equal to a specified value. In this case we want the Track's playTime property to be no greater than the value passed in to the method. We add this to our set of desired criteria.

NOTE

Just like HQL, expressions are always in terms of object properties, not table columns .

We'll look at some other Criterion types available through Expression in the next section. Appendix B lists them all, and you can create your own implementations of the Criterion interface if you've got something new you want to support.

Running the query this time gives us just the tracks that are no more than seven minutes long, as requested by the main() method (Example 8-4).

Example 8-4. Results of our complete simple criteria query
%

ant qtest

...

qtest:

    [java] Track: "Russian Trance" (PPK) 00:03:30, from Compact Disc

    [java] Track: "Video Killed the Radio Star" (The Buggles) 00:03:49, from VHS

Videocassette Tape

    [java] Track: "Gravity's Angel" (Laurie Anderson) 00:06:06, from Compact Disc

    [java] Track: "Adagio for Strings (Ferry Corsten Remix)" (Ferry Corsten,

William Orbit, Samuel Barber) 00:06:35, from Compact Disc

    [java] Track: "Test Tone 1" 00:00:10

    [java]   Comment: Pink noise to test equalization

A surprising number of the queries used to retrieve objects in real applications are very simple, and criteria queries are an extremely natural and compact way of expressing them in Java. Our new tracksNoLongerThan() method is actually shorter than it was in Example 3-10, and that version required a separate query (Example 3-9) to be added to the mapping document as well! Both approaches lead to the same patterns of underlying database access, so they are equally efficient at runtime.

In fact, you can make the code even more compact. The add() and createCriteria() methods return the Criteria instance, so you can continue to manipulate it in the same Java statement. Taking advantage of that, we can boil the method down to the version in Example 8-5.

Example 8-5. An even more compact version of our criteria query
public static List tracksNoLongerThan(Time length, Session session)

    throws HibernateException

{

return session.createCriteria(Track.class).

       add(Expression.le("playTime", length)).list();

}

The style you choose is a trade-off between space and readability (although some people may find the compact, run-on version even more readable). Even though this is marked as an experimental API, it already looks extremely useful, and I expect to adopt it in many places.

8.1.2 What about...

...Sorting the list of results, or retrieving a subset of all matching objects? Like the Query interface, the Criteria interface lets you limit the number of results you get back (and choose where to start) by calling setMaxResults() and setFirstResult() . It also lets you control the order in which they're returned (which you'd do using an order by clause in an HQL query), as shown in Example 8-6.

Example 8-6. Sorting the results by title
public static List tracksNoLongerThan(Time length, Session session)

    throws HibernateException

{

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

    criteria.add(Expression.le("playTime", length));

    criteria.addOrder(Order.asc("title"));

    return criteria.list();

}

The Order class is just a way of representing orderings. It has two static factory methods, asc() and desc() , for creating ascending or descending orderings respectively. Each takes the name of the property to be sorted. The results of running this version are in Example 8-7.

Example 8-7. The sorted results
%

ant qtest

...

qtest:

    [java] Track: "Adagio for Strings (Ferry Corsten Remix)" (Ferry Corsten,

William Orbit, Samuel Barber) 00:06:35, from Compact Disc

    [java] Track: "Gravity's Angel" (Laurie Anderson) 00:06:06, from Compact Disc

    [java] Track: "Russian Trance" (PPK) 00:03:30, from Compact Disc

    [java] Track: "Test Tone 1" 00:00:10

    [java]   Comment: Pink noise to test equalization

    [java] Track: "Video Killed the Radio Star" (The Buggles) 00:03:49, from VHS

Videocassette Tape

You can add more than one Order to the Criteria , and it will sort by each of them in turn (the first gets priority, and then if there are any results with the same value for that property, the second ordering is applied to them, and so on).