Chapter 8. Criteria Queries

Relational query languages like HQL (and SQL, on which HQL is 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 (very minor) disadvantages. Expanding long query expressions into a Java API makes them take up more room, and they’ll be less familiar to experienced database developers than a SQL-like query. The most serious deficiencies that existed in the API when the first version of this book was written have been solved in Hibernate 3. Formerly there were some things you simply couldn’t express using the 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). We’ll show you how you now can do these things. The next chapter shows how to accomplish similar tasks using Hibernate’s object-oriented query language.

Regardless of which approach you use to formulate queries with Hibernate, what comes out in the end are database-specific SQL statements which implement your intentions. This can be blissfully invisible to you, or if you’re interested in such nuts and bolts, you can turn on SQL logging using the show_sql property discussed in Example 3-1, or play with an interactive SQL preview within Eclipse, which will be shown in Chapter 11.

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-11 and updating the code of Example 3-12.

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.

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. Even if you’ve been following along, running schema and ctest will make sure you’ve got just the data these examples show.

Example 8-1. The beginnings of a criteria query
public static List tracksNoLongerThan(Time length, Session session) {
    Criteria criteria = session.createCriteria(Track.class);
    return criteria.list();
}

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 (see 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 VH
S Videocassette tape
     [java] Track: "Gravity's Angel" (Laurie Anderson) 00:06:06, from Compact Di
sc
     [java] Track: "Adagio for Strings (Ferry Corsten Remix)" (Ferry Corsten, Wi
lliam Orbit, Samuel Barber) 00:06:35, from Compact Disc
     [java] Track: "Adagio for Strings (ATB Remix)" (William Orbit, ATB, Samuel 
Barber) 00:07:39, from Compact Disc
     [java] Track: "The World '99" (Ferry Corsten, Pulp Victim) 00:07:05, from D
igital 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 org.hibernate.criterion.*;

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

Example 8-3. Criteria query that fully replaces the HQL version from Chapter 3
public static List tracksNoLongerThan(Time length, Session session) {
    Criteria criteria = session.createCriteria(Track.class);
    criteria.add(Restrictions.le("playTime", length));
    return criteria.list();
}

Note

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

The Restrictions 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.

We’ll look at some other Criterion types available through Restrictions 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 (see 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 VH
S Videocassette tape
     [java] Track: "Gravity's Angel" (Laurie Anderson) 00:06:06, from Compact Di
sc
     [java] Track: "Adagio for Strings (Ferry Corsten Remix)" (Ferry Corsten, Sa
muel Barber, William Orbit) 00:06:35, from Compact Disc
     [java] Track: "Test Tone 1" 00:00:10
     [java]   Comment: Pink noise to test equalization

Note

While I revised this example for the new book, the “Adagio for Strings” remix started randomly playing in iTunes.

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-12, and that version required a separate query (Example 3-11) 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.

If possible, however, to 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 shown in Example 8-5.

Example 8-5. An even more compact version of our criteria query
public static List tracksNoLongerThan(Time length, Session session) {
    return session.createCriteria(Track.class).
        add(Restrictions.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).

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() as well as a variety of other scrolling and manipulation methods. It also lets you control the order in which results are 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) {
    Criteria criteria = session.createCriteria(Track.class);
    criteria.add(Restrictions.le("playTime", length));
    criteria.addOrder(Order.asc("title").ignoreCase());
    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. If you call the ignoreCase() method on an Order instance, it will sort capital and lowercase letters together, which is often what you want for display. 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, Sa
muel Barber, William Orbit) 00:06:35, from Compact Disc
     [java] Track: "Gravity's Angel" (Laurie Anderson) 00:06:06, from Compact Di
sc
     [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 VH
S 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).

Compounding Criteria

As you might expect, you can add more than one Criterion to your query, and all of them must be satisfied for objects to be included in the results. This is equivalent to building a compound criterion using Restrictions.conjunction(), described in Appendix B. As shown in Example 8-8, we can restrict our results so that the tracks also have to contain a capital “A” somewhere in their title by adding another line to our method.

Example 8-8. A pickier list of short tracks
Criteria criteria = session.createCriteria(Track.class);
criteria.add(Restrictions.le("playTime", length));
criteria.add(Restrictions.like("title", "%A%"));
criteria.addOrder(Order.asc("title").ignoreCase());
return criteria.list();

With this in place, we get fewer results, as Example 8-9 shows.

Example 8-9. Tracks of seven minutes or less containing a capital “A” in their titles
qtest:
     [java] Track: "Adagio for Strings (Ferry Corsten Remix)" (Samuel Barber, Fe
rry Corsten, William Orbit) 00:06:35, from Compact Disc
     [java] Track: "Gravity's Angel" (Laurie Anderson) 00:06:06, from Compact Di
sc

If you don’t even want to remember (or learn) the SQL%” syntax for string matching with like, Hibernate provides a variation of the factory you can call to express in Java the kind of matching you want—in this case, it would be Restrictions.like(“title”, “A”, MatchMode.ANYWHERE). If you want to do case-insensitive matching, use ilike instead of like.

If you want to find any objects matching any one of your criteria, rather than requiring them to fit all criteria, you need to explicitly use Restrictions.disjunction() to group them. You can build up combinations of such groupings, and other complex hierarchies, using the criteria factories offered by the Restrictions class. Check Appendix B for the details. Example 8-10 shows how we’d change the sample query to give us tracks that either met the length restriction or contained a capital “A.”

Note

Criteria queries offer a surprising mix of power and convenience.

Example 8-10. Picking tracks more leniently
Criteria criteria = session.createCriteria(Track.class);
Disjunction any = Restrictions.disjunction();
any.add(Restrictions.le("playTime", length));
any.add(Restrictions.like("title", "%A%"));
criteria.add(any);
criteria.addOrder(Order.asc("title").ignoreCase());
return criteria.list();

This results in us picking up a new version of “Adagio for Strings” (see Example 8-11).

Example 8-11. Tracks whose title contains the letter “A,” or whose length is seven minutes or less
qtest:
    [java] Track: "Adagio for Strings (ATB Remix)" (ATB, William Orbit, Samuel
Barber) 00:07:39, from Compact Disc
    [java] Track: "Adagio for Strings (Ferry Corsten Remix)" (Ferry Corsten, Wi
lliam Orbit, Samuel Barber) 00:06:35, from Compact Disc
    [java] Track: "Gravity's Angel" (Laurie Anderson) 00:06:06, from Compact Di
sc
    [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 VH
S Videocassette Tape

Finally, note that it’s still possible, thanks to the clever return values of these methods, to consolidate our method into a single expression (see Example 8-12).

Example 8-12. Taking code compactness a bit too far
return session.createCriteria(Track.class).add(Restrictions.disjunction().
        add(Restrictions.le("playTime", length)).
        add(Restrictions.like("title", "%A%"))).
    addOrder(Order.asc("title").ignoreCase()).list();

Although this yields the same results, I hope you agree it doesn’t do good things for the readability of the method (except perhaps for LISP experts)!

You can use the facilities in Restrictions to build up a wide variety of multipart criteria. Some things still require HQL, and past a certain threshold of complexity, you may be better off in that environment. But, you can do a lot with criteria queries, and they’re often the right way to go.

Projection and Aggregation with Criteria

If you’re familiar with SQL, you know what this section title means. If not, fear not; it’s actually pretty simple. Projection simply means that you don’t need all of the information available in a table, so you want to request just part of it. In the case of an object-oriented environment like Hibernate, it means that you don’t need to retrieve an entire object, just one or two of its properties. Aggregation similarly involves identifying properties, but then allows you to ask for statistical information about those properties, such as counting values, or finding maximum, minimum, or average values.

Before Hibernate 3, neither of these were possible without using HQL, so they are a nice addition to the Criteria API. Let’s look at some examples. Starting simply, suppose we want to print all the track titles that contain the letter “v” without bothering to load any entire Track objects.

How do I do that?

Example 8-13 shows a method that uses the projection capability of the criteria API to achieve this.

Example 8-13. Simple projection on a single property
/**
 * Retrieve the titles of any tracks that contain a particular text string.
 *
 * @param text the text to be matched, ignoring case, anywhere in the title.
 * @param session the Hibernate session that can retrieve data.
 * @return the matching titles, as strings.
 */
public static List titlesContainingText(String text, Session session) {
    Criteria criteria = session.createCriteria(Track.class);
    criteria.add(Restrictions.like("title", text, MatchMode.ANYWHERE). 1
            ignoreCase());
    criteria.setProjection(Projections.property("title")); 2
    return criteria.list();
}
1

This is an illustration of using the MatchMode interface to avoid having to perform string manipulation or remember the “%” character for specifying the kind of string matching desired.

2

This use of the Projections class is how we tell the criteria we want a projection, and that we’re specifically interested in retrieving the title property of the tracks we find.

So this method, like our other criteria queries, returns a List. But a list of what, exactly? The criteria are created on the Track class, but it wouldn’t make sense to construct and return Track objects since we are only retrieving the title property. In fact, in situations like this where you project on a single property, you get a list of whatever class is associated with that property.

Note

That makes sense!

In this case, since it is a string property, you get a List of String instances.

We can easily check this by adding this method to QueryTest.java and changing main() to call it as follows:

System.out.println(titlesContainingText("v", session));

Running this version produces the following output:

qtest:
     [java] [Video Killed the Radio Star, Gravity's Angel]

It should be clear that projections can retrieve a different property than the one(s) you use in the restrictions. If we retrieved the track lengths instead, like so:

criteria.setProjection(Projections.property("playTime"));

we’d get this output:

qtest:
     [java] [00:03:49, 00:06:06]

Of course, the method name would be wrong then, and the output is kind of cryptic. What if we wanted to retrieve both the titles and lengths? It turns out that’s almost as easy, as shown in Example 8-14.

Example 8-14. Projection on two properties
/**
 * Retrieve the titles and play times of any tracks that contain a
 * particular text string.
 * 
 * @param text the text to be matched, ignoring case, anywhere in the title.
 * @param session the Hibernate session that can retrieve data.
 * @return the matching titles and times wrapped in object arrays.
 */
public static List titlesContainingTextWithPlayTimes(String text,
                                                     Session session) {
    Criteria criteria = session.createCriteria(Track.class);
    criteria.add(Restrictions.like("title", text, MatchMode.ANYWHERE)
            .ignoreCase());
    criteria.setProjection(Projections.projectionList(). 1
            add(Projections.property("title")). 2
            add(Projections.property("playTime")));
    return criteria.list();
}
1

The projectionList() method creates a ProjectionList instance that can contain multiple projection choices for a single criteria query. Note that we’re using the compact chaining notation introduced in Example 8-5 to avoid the need to declare a variable to hold this instance.

2

Then we just add all the projections we want to the ProjectionList, and that gets passed to the query’s setProjection() method.

The trickiest part here is what gets returned from the query. As always, it is a List, but now each element is going to contain more than one value, of potentially different types. Hibernate chooses to return a list of Object arrays. This makes the code for display somewhat more awkward:

for (Object o : titlesContainingTextWithPlayTimes("v", session)) {
    Object[] array = (Object[])o;
    System.out.println("Title: " + array[0] +
            " (Play Time: " + array[1] + ')'),
}

This produces the following output:

qtest:
     [java] Title: Video Killed the Radio Star (Play Time: 00:03:49)
     [java] Title: Gravity's Angel (Play Time: 00:06:06)

But really, the code is ugly; you’d never really use projection this way. The whole point of an object/relational mapping system is that you can just return the object, and then use that to get at the properties you want when you want more than one of them.

So, why would projection support multiple values? Well, there is actually a good reason, and it has to do with that notion of aggregation that we introduced at the start of this section. Most of the time you are interested in using projection, you will be getting values that aren’t directly object properties at all, but that are based on those properties. Example 8-15 shows a method that prints out, for each type of media represented in the database, the number of tracks that came from that media, and the longest play time of any track from that media.

Example 8-15. Projection with aggregation
/**
 * Print statistics about various media types.
 * 
 * @param session the Hibernate session that can retrieve data.
 */
public static void printMediaStatistics(Session session) {
    Criteria criteria = session.createCriteria(Track.class);
    criteria.setProjection(Projections.projectionList(). 1
            add(Projections.groupProperty("sourceMedia")). 2
            add(Projections.rowCount()). 3
            add(Projections.max("playTime"))); 4

    for (Object o : criteria.list()) { 5
        Object[] array = (Object[])o;
        System.out.println(array[0] + " track count: " + array[1] +
                "; max play time: " + array[2]);
    }
}

Note

Now we’re doing interesting things with the power of the database!

This example builds on many of the things we’ve seen so far:

1

As before, we are creating a ProjectionList to hold the various items we want our query to return.

2

The groupProperty() method is similar to the property() method we’ve used so far, but it tells Hibernate to group all rows with the same value for the specified property into a single row in our results. This is the key to performing aggregation, and allows us to add aggregate values to the projection.

3

The rowCount() projection doesn’t need any arguments, because it just returns the number of rows that were grouped into the current set of results (based on the value of our groupProperty(), sourceMedia). This is how we count the tracks for each media type.

4

The max() projection returns the largest value found for its property in each grouped set of results.

5

Finally, we have an output loop like the one we created for the previous example, which loops over the List of Object arrays returned by the criteria query, and prints them out.

Setting up main() in QueryTest.java to call this method is simple:

printMediaStatistics(session);

And it produces the following output:

qtest:
     [java] CD track count: 4; max play time: 00:07:39
     [java] VHS track count: 1; max play time: 00:03:49
     [java] STREAM track count: 1; max play time: 00:07:05
     [java] null track count: 1; max play time: 00:00:10

That should give you a sense of the real value of projection and aggregation. (That null media type was our test tone.)

Notice that this output is not sorted in any particular order, though. We might want to do that, but how do you sort on a projection? To answer that we need to look at another refinement in the Criteria API. You can assign aliases to objects and properties with which you’re working (just as you can for tables and columns in database query languages), whether they come directly from the database, or from projection and aggregation. So to get things sorted nicely by source media, we need to start by adding an alias to our grouped property:

add(Projections.groupProperty("sourceMedia").as("media")).

This declares the alias media which we can then use for sorting, as we did previously with ordinary object properties:

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

With these changes, we see sorted output:

qtest:
     [java] null track count: 1; max play time: 00:00:10
     [java] CD track count: 4; max play time: 00:07:39
     [java] STREAM track count: 1; max play time: 00:07:05
     [java] VHS track count: 1; max play time: 00:03:49

There are other reasons for using aliases, and more things you can do with projection, but it’s time to look at other aspects of criteria. We’ll show you where you can learn more about all this at the end of the chapter.

Applying Criteria to Associations

So far we’ve been looking at the properties of a single class in forming our criteria. Of course, in our real systems, we’ve got a rich set of associations between objects, and sometimes the details we want to use to filter our results come from these associations. Fortunately, the criteria query API provides a straightforward way of performing such searches.

How do I do that?

Let’s suppose we’re interested in finding all the tracks associated with particular artists. We’d want our criteria to look at the values contained in each Track’s artists property, which is a collection of associations to Artist objects. Just to make it a bit more fun, let’s say we want to be able to find tracks associated with artists whose name property matches a particular substring pattern.

Let’s add a new method to QueryTest.java to implement this. Add the method shown in Example 8-16 after the end of the tracksNoLongerThan() method.

Example 8-16. Filtering tracks based on their artist associations
/**
 * Retrieve any tracks associated with artists whose name matches a SQL
 * string pattern.
 * 
 * @param namePattern the pattern which an artist's name must match
 * @param session the Hibernate session that can retrieve data.
 * @return a list of {@link Track}s meeting the artist name restriction.
 */
public static List tracksWithArtistLike(String namePattern, Session session)
{
    Criteria criteria = session.createCriteria(Track.class);
    Criteria artistCriteria = criteria.createCriteria("artists"); 1
    artistCriteria.add(Restrictions.like("name", namePattern)); 2
    artistCriteria.addOrder(Order.asc("name").ignoreCase()); 3
    return criteria.list();
}
1

Things started out looking familiar, but this line creates a second Criteria instance, attached to the one we’re using to select tracks, by following the tracks’ artists property. This means we can add constraints to either criteria (which would apply to the properties of the Track itself), or to artistCriteria, which causes them to apply to the properties of the Artist entities associated with the track.

2

Note

Finally, in this version of the book, we can see the output I’d originally hoped for.

In this case, we are only interested in features of the artists, so we restrict our results to tracks associated with at least one artist whose name matches the specified pattern. (Again, by applying constraints to both Criteria, we could have restricted by both Track and Artist properties.)

3

We request sorting by artist name. This is another improvement Hibernate 3 offers over the experimental Criteria API that was available when the first version of this book was written. Back then, you could only sort the outermost criteria, not the subcriteria you created for associations. If you tried, you were rewarded with an UnsupportedOperationException.

To see all this in action, we need to make one more change. Modify the main() method so that it invokes this new query, as shown in Example 8-17.

Example 8-17. Calling the new track artist name query
...
// Ask for a session using the JDBC information we've configured
Session session = sessionFactory.openSession();
try {
    // Print tracks associated with an artist whose name ends with "n"
    List tracks = tracksWithArtistLike("%n", session);
    for (ListIterator iter = tracks.listIterator() ;
...

Running ant qtest now gives the results shown in Example 8-18.

Example 8-18. Tracks associated with an artist whose name ends with the letter “n”
qtest:
     [java] Track: "The World '99" (Pulp Victim, Ferry Corsten) 00:07:05, from D
igital Audio Stream
     [java] Track: "Adagio for Strings (Ferry Corsten Remix)" (William Orbit, Sa
muel Barber, Ferry Corsten) 00:06:35, from Compact Disc
     [java] Track: "Gravity's Angel" (Laurie Anderson) 00:06:06, from Compact Di
sc

What just happened?

Note

You can also create aliases for the associations with which you’re working, and use those aliases in expressions. This starts getting complex but it’s useful. Explore it someday.

If you look at the lists of artists for each of the three tracks that were found, you’ll see that at least one of them has a name ending in “n” as we requested (they’re in bold type for easier recognition in this printing). Also notice that we have access to all the artists associated with each track, not just the ones that matched the name criterion. This is what you’d expect and want, given that we’ve retrieved the actual Track entities. You can run criteria queries in a different mode, by calling setResultTransformer(Criteria.ALIAS_TO_ENTITY_MAP), which causes it to return a list of hierarchical Maps in which the criteria at each level have filtered the results. This goes beyond the scope of this book, but there are some examples of it in the reference and API documentation.

Tip

If the table from which you’re fetching objects might contain duplicate entries, you can achieve the equivalent of SQL’s “select distinct” by calling setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY) on your criteria.

Querying by Example

If you don’t want to worry about setting up expressions and criteria, but you’ve got an object that shows what you’re looking for, you can use it as an example and have Hibernate build the criteria for you.

How do I do that?

Let’s add another query method to QueryTest.java. Add the code of Example 8-19 to the top of the class where the other queries are.

Example 8-19. Using an example entity to populate a criteria query
/**
 * Retrieve any tracks that were obtained from a particular source media
 * type.
 * 
 * @param media the media type of interest.
 * @param session the Hibernate session that can retrieve data.
 * @return a list of {@link Track}s meeting the media restriction.
 */
public static List tracksFromMedia(SourceMedia media, Session session) {
    Track track = new Track(); 1
    track.setSourceMedia(media);
    Example example = Example.create(track); 2

    Criteria criteria = session.createCriteria(Track.class); 3
    criteria.add(example);
    criteria.addOrder(Order.asc("title"));
    return criteria.list();
}
1

We start by creating the example Track and set the sourceMedia property to represent what we’re looking for.

2

Then we wrap it in an Example object. This object gives you some control over which properties will be used in building criteria and how strings are matched. The default behavior is that null properties are ignored, and that strings are compared in a case-sensitive and literal way. You can call example’s excludeZeroes() method if you want properties with a value of zero to be ignored, too, or excludeNone() if even null properties are to be matched. An excludeProperty() method lets you explicitly ignore specific properties by name, but that’s starting to get a lot like building criteria by hand. To tune string handling, there are ignoreCase() and enableLike() methods, which do just what they sound like.

3

Then we create a criteria query, just like our other examples in this chapter, except that we add our example to it instead of using Restrictions to create a Criterion. Hibernate takes care of translating example into the corresponding criteria. The remaining lines are just like our previous query methods: setting up a sort order, running the query, and returning the list of matching entities.

Once again we need to modify the main() method to call our new query. Let’s find the tracks that came from CDs. Make the changes shown in Example 8-20.

Example 8-20. Changes to main() to call our example-driven query method
...
// Ask for a session using the JDBC information we've configured
Session session = sessionFactory.openSession();
try {
    // Print tracks that came from CDs
    List tracks = tracksFromMedia(SourceMedia.CD, session);
    for (ListIterator iter = tracks.listIterator() ;
...

Running this version produces output like Example 8-21.

Example 8-21. Results of querying by example for tracks from CDs
     [java] Track: "Adagio for Strings (ATB Remix)" (ATB, Samuel Barber, William
 Orbit) 00:07:39, from Compact Disc
     [java] Track: "Adagio for Strings (Ferry Corsten Remix)" (Samuel Barber, Wi
lliam Orbit, Ferry Corsten) 00:06:35, from Compact Disc
     [java] Track: "Gravity's Angel" (Laurie Anderson) 00:06:06, from Compact Di
sc
     [java] Track: "Russian Trance" (PPK) 00:03:30, from Compact Disc

Tip

You might think this is something of a contrived example, in that we didn’t actually have a handy Track object around to use as an example and had to create one in the method. Well, perhaps, but there is a valuable reason to use this approach: it gives you even more compile-time checking than pure criteria queries. While any criteria query protects against syntactic runtime errors in HQL queries, you can still make mistakes in your property names, which won’t be caught by the compiler, since they’re just strings. When building example queries, you actually set property values using the mutator methods of the entity classes. This means if you make a typo, Java catches it at compile time.

As you might expect, you can use examples with subcriteria for associated objects, too. We could rewrite tracksWithArtistLike() so that it uses an example Artist rather than building its criterion “by hand.” We’ll need to call enableLike() on our example. Example 8-22 shows a concise way of doing this.

Example 8-22. Updating the artist name query to use an example artist
public static List tracksWithArtistLike(String namePattern, Session session)
{
    Criteria criteria = session.createCriteria(Track.class);
    Example example = Example.create(new Artist(namePattern, null, null));
    criteria.createCriteria("artists").add(example.enableLike()).addOrder(
        Order.asc("name").ignoreCase());
    return criteria.list();
}

This produces exactly the same output as when we were constructing the inner criteria “by hand.” Remember that if you want to try running this you’ll need to switch main() back to the way it was in Example 8-17.

Note

Criteria queries are pretty neat, aren’t they? I liked them a lot more than I expected even in Hibernate 2, and they are more powerful now.

A great variety of queries that power the user interface and general operation of a typical data-driven Java application can be expressed as criteria queries, and they provide advantages in readability, compile-time type checking, and even (surprisingly) compactness. As far as new database APIs go, I’d call this a winner.

Property-Oriented Criteria Factories

We’ve already seen that there is often more than one way to express what you want using the criteria API, depending on your stylistic preferences, or the way you think about things. The Property class offers another bunch of alternatives of which you should be aware. We will not explore this class in depth because it is just another way of setting up criteria, but it is important to explain how it works, so you won’t be confused if you run across examples, or feel baffled while scanning Hibernate’s dense JavaDoc. (And, frankly, after one or two examples, you’ll undoubtedly get the idea well enough that you may decide to adopt this approach yourself.)

Property is another factory for criteria, much like Restrictions, which we’ve been using in this chapter (and Order and Projection, for that matter). You can create essentially all the query refinements available with those other factories by using Property instead. Rather than starting with the kind of constraint in which you’re interested, and then naming the property to which you want to apply it, you instead start with the property and pick a constraint.

Note

Enough abstraction! Show some examples!

As before, you start by creating Criteria on the object you want to query. But instead of saying, for example:

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

you can say:

criteria.add(Property.forName("playTime").le(length));

It’s really very similar—just a slightly different emphasis—as when you have more than one way to phrase the same concept in English. There are a bunch of methods in Property that give you criteria, orderings, and projections. You can’t construct a Property instance using new()—you need to either start with the forName() static factory, or use an existing Property instance and call getProperty() on it to traverse to one of its component properties.

Here are a few more examples to show how this approach fits in. Where we had used statements like:

criteria.addOrder(Order.asc("name").ignoreCase());

we could instead have used:

criteria.addOrder(Property.forName("name").asc().ignoreCase());

And with projections, the approach we’d followed, such as:

criteria.setProjection(Projections.max("playTime"));

could equally well have been expressed as:

criteria.setProjection(Property.forName("playTime").max());

Note

This is almost an embarrassment of riches!

So, take your pick. Sometimes the kind of problem you’re solving, or the thrust of the rest of the code, will evoke one style or the other. Or perhaps you’ll just like one better and stick with it. But at least you’re now aware you might run into both, and should be able to understand any criteria expression. These factory methods are all summarized in Appendix B.

Overwhelmed with options yet? No? Well, when criteria queries don’t quite do the job, or you want an even more extreme alternative to all the choices you’ve seen in this chapter—especially if you’re comfortable with the concision of SQL—you can turn to the full power of HQL. We investigate that in the next chapter.

What about…

…sneaking a little SQL in with your other criteria to take advantage of database-specific features or your mad DBA skillz? Using subqueries, or detached criteria that can be set up before you have a Hibernate session? Plugging in your own Java code to filter the results as the query is performed? All of these things and more are possible, but beyond the scope of this book. If you’re ready for them, the Advanced query options chapter in Java Persistence with Hibernate is a good overview, and the Hibernate JavaDoc and source code are the definitive references.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset