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.
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.
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.
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.
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).
% 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.
public static List tracksNoLongerThan(Time length, Session session) {
Criteria criteria = session.createCriteria(Track.class);
criteria.add(Restrictions.le("playTime", length));
return criteria.list();
}
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).
% 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
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.
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).
…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.
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.
% 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).
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.
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.
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.”
Criteria queries offer a surprising mix of power and convenience.
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).
qtest: [java] Track: "A
dagio 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).
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.
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.
Example 8-13 shows a method that uses the projection capability of the criteria API to achieve this.
/** * 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). ignoreCase()); criteria.setProjection(Projections.property("title")); return criteria.list(); }
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.
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.
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.
/** * 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(). add(Projections.property("title")). add(Projections.property("playTime"))); return criteria.list(); }
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.
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.
/** * 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(). add(Projections.groupProperty("sourceMedia")). add(Projections.rowCount()). add(Projections.max("playTime"))); for (Object o : criteria.list()) { Object[] array = (Object[])o; System.out.println(array[0] + " track count: " + array[1] + "; max play time: " + array[2]); } }
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:
As before, we are creating a ProjectionList
to hold the
various items we want our query to return.
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.
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.
The max()
projection returns
the largest value found for its property in each grouped set of results.
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.
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.
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.
/** * 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"); artistCriteria.add(Restrictions.like("name", namePattern)); artistCriteria.addOrder(Order.asc("name").ignoreCase()); return criteria.list(); }
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.
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.)
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.
... // Ask for a session using the JDBC information we've configured Session session = sessionFactory.openSession(); try { // Printtracks 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.
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
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 Map
s 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.
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.
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.
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.
/** * 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(); track.setSourceMedia(media); Example example = Example.create(track); Criteria criteria = session.createCriteria(Track.class); criteria.add(example); criteria.addOrder(Order.asc("title")); return criteria.list(); }
We start by creating the example Track
and set the sourceMedia
property
to represent what we’re looking for.
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.
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.
... // Ask for a session using the JDBC information we've configured Session session = sessionFactory.openSession(); try { // Printtracks that came from CDs
List tracks =tracksFromMedia(SourceMedia.CD,
session); for (ListIterator iter = tracks.listIterator() ; ...
Running this version produces output like Example 8-21.
[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, fromCompact Di
sc
[java] Track: "Russian Trance" (PPK) 00:03:30, fromCompact Disc
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.
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.
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.
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.
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());
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());
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.
…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.