3.2 Finding Persistent Objects
It's time to throw the giant lever into reverse and look at how you load data from a database into Java objects.
Use Hibernate Query Language to get an object-oriented view of the contents of your mapped database tables. These might have started out as objects
persisted
in a previous session, or they might be data that came from completely outside your application code.
3.2.1 How do I do that?
Example 3-5 shows a program that runs a simple query using the test data we just created. The overall structure will look very familiar, because all the Hibernate setup is the same as the previous program.
Example 3-5. QueryTest.java
1 package com.oreilly.hh;
2
3 import net.sf.hibernate.*;
4 import net.sf.hibernate.cfg.Configuration;
5
6 import java.sql.Time;
7 import java.util.*;
8
9 /**
10 * Retrieve data as objects
11 */
12 public class QueryTest {
13
14 /**
15 * Retrieve any tracks that fit in the specified amount of time.
16 *
17 * @param length the maximum playing time for tracks to be returned.
18 * @param session the Hibernate session that can retrieve data.
19 * @return a list of {@link Track}s meeting the length restriction.
20 * @throws HibernateException if there is a problem.
21 */
22 public static List tracksNoLongerThan(Time length, Session session)
23 throws HibernateException
24 {
25 return session.find("from com.oreilly.hh.Track as track " +
26 "where track.playTime <= ?",
27 length, Hibernate.TIME);
28 }
29
30 /**
31 * Look up and print some tracks when invoked from the command line.
32 */
33 public static void main(String args[]) throws Exception {
34 // Create a configuration based on the properties file we've put
35 // in the standard place.
36 Configuration config = new Configuration();
37
38 // Tell it about the classes we want mapped, taking advantage of
39 // the way we've named their mapping documents.
40 config.addClass(Track.class);
41
42 // Get the session factory we can use for persistence
43 SessionFactory sessionFactory = config.buildSessionFactory();
44
45 // Ask for a session using the JDBC information we've configured
46 Session session = sessionFactory.openSession();
47 try {
48 // Print the tracks that will fit in five minutes
49 List tracks = tracksNoLongerThan(Time.valueOf("00:05:00"),
50 session);
51 for (ListIterator iter = tracks.listIterator() ;
52 iter.hasNext() ; ) {
53 Track aTrack = (Track)iter.next();
54 System.out.println("Track: \"" + aTrack.getTitle() +
55 "\", " + aTrack.getPlayTime());
56 }
57 } finally {
58 // No matter what, close the session
59 session.close();
60 }
61
62 // Clean up after ourselves
63 sessionFactory.close();
64 }
65 }
Again, add a target (Example 3-6) at the end of
build.xml
to run this test.
Example 3-6. Ant target to invoke our query test
<target name="qtest" description="Run a simple Hibernate query"
depends="compile">
<java classname="com.oreilly.hh.QueryTest" fork="true">
<classpath refid="project.class.path"/>
</java>
</target>
With this in place, we can simply type
ant qtest
to retrieve and display some data, with the results shown in Example 3-7. To save space in the output, we've edited our
log4j.properties
to
turn
off all the '
Info
' messages, since they're no different than in the previous example. You can do this yourself by changing the line:
log4j.logger.net.sf.hibernate=info
to replace the word
info
with warn:
log4j.logger.net.sf.hibernate=
warn
Example 3-7. Running the query test
%
ant qtest
Buildfile: build.xml
prepare:
compile:
[javac] Compiling 1 source file to /Users/jim/Documents/Work/OReilly/
Hibernate/Examples/ch03/classes
qtest:
[java] Track: "Russian Trance", 00:03:30
[java] Track: "Video Killed the Radio Star", 00:03:49
BUILD SUCCESSFUL
Total time: 11 seconds
3.2.2 What just
happened
?
In Example 3-5, we started out by defining a utility method,
tracksNoLongerThan()
on lines 14-28, which
performs
the actual Hibernate query. It retrieves any tracks whose playing time is less than or equal to the amount specified as a parameter. Hibernate queries are written in HQL (Hibernate Query Language ”explored in Chapter 9). This is a SQL-inspired but object-oriented query language with important differences from SQL. HQL is explored in Chapter 9. For now, note that it supports parameter placeholders, much like
PreparedStatement
in JDBC. And, just like in that environment, using them is preferable to
putting
together queries through string manipulation. As you'll see below, however, Hibernate offers even better ways of working with queries in Java.
NOTE
If you don't want to deal with any query syntax at all, check out criteria queries in Chapter 8!
The query itself looks a little
strange
. It starts with '
from
' rather than '
select
something
' as you might expect. While you can
certainly
use the more familiar format, and will do so when you want to pull out individual properties from an object in your query, if you want to retrieve entire objects you can use this more abbreviated syntax.
NOTE
When you're working with pre-existing databases and objects, it's important to realize that HQL queries refer to object properties rather than database table
Also note that the query is
expressed
in terms of the mapped Java
objects
and
properties
rather than the tables and
columns
. Since the object being used in the query is in the same package as the mapping document, we don't actually need to use a fully qualified class name like this, but it helps
remind
readers of the query that it's expressed in terms of objects. Keeping the names consistent is a
fairly
natural choice, and this will always be the case when you're using Hibernate to generate the schema and the data objects, unless you tell it explictly to use different column
names
.
Also, in HQL ”just as in SQL ”you can alias a column or table to another name; in fact, you
must
use aliases to refer to specific JavaBeans properties of mapped classes in your
where
clauses.
With all that in mind, the meaning of the HQL query '
from com.oreilly.hh.Track as track where track.playTime <= ?'
breaks down like this:
-
We're retrieving instances of our persistent Java data bean
Track
. (This happens to be stored in the database table called
TRACK
, but that is managed by the mapping document for class
Track
; the table could be called anything.) Since we started the query with the from clause (rather than a
select
clause), we want to obtain entire
Track
instances as the result of the query.
-
Within the query, we are defining an alias '
track
' to refer to one particular instance of the
Track
class at a time, so we can specify the properties of the instances we'd like to retrieve.
-
The instances we want are those where the
playTime
property has a value that's less than or equal to the first (and only) query parameter we're passing in at runtime. (Once again, the fact that the
playTime
property is mapped to the
PLAYTIME
column is a detail that is buried in the
Track
mapping document, and it need not be true.)
HQL reads a lot like SQL, but the semantics are different ”they refer to the world of the Java program rather than the world of the relational database. This will be more clear in later examples and in Chapter 9 where we perform more complex queries.
The rest of the program should look
mighty
familiar from the previous example. Our
try
block is simplified because we don't need a transaction, as we're not changing any data. We still use one so that we can have a
finally
clause to close our session cleanly. The body is quite simple, calling our query method to request any tracks whose playing time is five minutes or less, and then iterating over the resulting
Track
objects, printing their titles and playing times.
3.2.3 What about...
...Deleting objects? If you've made changes to your data creation script and want to start with a 'clean slate' in the form of an empty database so you can test them, all you need to do is run
ant schema
again. This will drop and recreate the
Track
table in a pristine and empty state. Don't do it unless you mean it!
If you want to be more selective about what you delete, you can either do it through SQL commands in the HSQLDB UI (
ant db
), or you can make a variant of the query test example that retrieves the objects you want to get rid of. Once you've got a reference to a persistent object, passing it to the
Session
's
delete()
method will remove it from the database:
session.delete(aTrack);
You've still got at least one reference to it in your program, until
aTrack
goes out of scope or gets reassigned, so conceptually the
easiest
way to understand what
delete()
does is to think of it as turning a persistent object back into a transient one.
Another way to use the
delete()
method is to pass it an HQL query string that matches multiple objects. This lets you delete many persisted objects at once, without writing your own loop. A Java-based alternative to
ant schema
, and a slightly less violent way of clearing out all the tracks, would therefore be something like this:
session.delete("from com.oreilly.hh.Track");
|
Don't forget that regardless of which of these approaches you use, you'll need to wrap the data manipulation code inside a Hibernate transaction, and commit the transaction if you want your changes to 'stick.'
|
|
|