O'Reilly logo

Harnessing Hibernate by James Elliott, Ryan Fowler, Timothy M. O'Brien

Stay ahead with the world's most comprehensive technology and business learning platform.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, tutorials, and more.

Start Free Trial

No credit card required

Persisting Collections

Our first task is to enhance the CreateTest class to take advantage of the new richness in our schema, creating some artists and associating them with tracks.

How do I do that?

To begin with, add some helper methods to CreateTest.java to simplify the task, as shown in Example 4-7 (with changes and additions in bold).

Example 4-7. Utility methods to help find and create artists, and link them to tracks

package com.oreilly.hh;

import org.hibernate.*;
import org.hibernate.cfg.Configuration;

import com.oreilly.hh.data.*;

import java.sql.Time;
import java.util.*; 1

 * Create more sample data, letting Hibernate persist it for us.
public class CreateTest {

   * Look up an artist record given a name.
   * @param name the name of the artist desired.
   * @param create controls whether a new record should be created if
   *        the specified artist is not yet in the database.
   * @param session the Hibernate session that can retrieve data
   * @return the artist with the specified name, or <code>null</code> if no
   *         such artist exists and <code>create</code> is <code>false</code>.
   * @throws HibernateException if there is a problem.
  public static Artist getArtist(String name, boolean create, Session session) 2
    Query query = session.getNamedQuery("com.oreilly.hh.artistByName");
    query.setString("name", name);
    Artist found = (Artist)query.uniqueResult(); 3
    if (found == null && create) { 4
      found = new Artist(name, new HashSet<Track>());
    return found;

   * Utility method to associate an artist with a track
  private static void addTrackArtist(Track track, Artist artist) { 5

As is so often the case when working with Hibernate, this code is pretty simple and self-explanatory:


We used to import java.util.Date, but we’re now importing the whole util package to work with Collections. The “*” is bold to highlight this, but it’s easy to miss when scanning the example.


We’ll want to reuse the same artists if we create multiple tracks for them—that’s the whole point of using an Artist object rather than just storing strings—so our getArtist() method does the work of looking them up by name.


The uniqueResult() method is a convenience feature of the Query interface, perfect in situations like this, where we know we’ll either get one result or none. It saves us the trouble of getting back a list of results, checking the length and extracting the first result if it’s there. We’ll either get back the single result or null if there were none. (We’ll be thrown an exception if there is more than one result—you might think our unique constraint on the column would prevent that, but SQL is case-sensitive, and our query is matching insensitively, so it’s up to us to be sure we always call getArtist() to see if an artist exists before creating a new record.)


So all we need to do is check for null and create a new Artist if we didn’t find one and the create flag indicates we’re supposed to.


If we left out the session.save() call, our artists would remain transient. (Itinerant painters? Sorry.) Hibernate is helpful enough to throw an exception if we try to commit our transaction in this state, by detecting references from persistent Track instances to transient Artist instances. You may want to review the lifecycle discussion in Chapter 3, and “Lifecycle Associations” in Chapter 5, which explores this in more depth.


The addTrackArtist() method is almost embarrassingly simple. It’s just ordinary Java Collections code that grabs the Set of artists belonging to a Track and adds the specified Artist to it. Can that really do everything we need? Where’s all the database manipulation code we normally have to write? Welcome to the wonderful world of object/relational mapping tools!

You might have noticed that getArtist() uses a named query to retrieve the Artist record. In Example 4-8, we will add that at the end of Artist.hbm.xml. (Actually, we could put it in any mapping file, but this is the most sensible place, since it relates to Artist records.)

Example 4-8. Artist lookup query to be added to the artist mapping document

<query name="com.oreilly.hh.artistByName">
        from Artist as artist
        where upper(artist.name) = upper(:name)

We use the upper() function to perform a case-insensitive comparison of artists’ names, so that we retrieve the artist even if the capitalization is different during lookup than what’s stored in the database. This sort of case-insensitive but preserving architecture, a user-friendly concession to the way humans like to work, is worth implementing whenever possible. Databases other than HSQLDB may have a different name for the function that converts strings to uppercase, but there should be one available. And we’ll see a nice Java-oriented, database-independent way of doing this sort of thing in Chapter 8.

Now we can use this infrastructure to actually create some tracks with linked artists. Example 4-9 shows the remainder of the CreateTest class with the additions marked in bold. Edit your copy to match (or download it to save the typing).

Example 4-9. Revisions to main() in CreateTest.java to add artist associations

  public static void main(String args[]) throws Exception {
    // Create a configuration based on the XML file we've put
    // in the standard place.
    Configuration config = new Configuration();

    // Get the session factory we can use for persistence
    SessionFactory sessionFactory = config.buildSessionFactory();

    // Ask for a session using the JDBC information we've configured
    Session session = sessionFactory.openSession();
    Transaction tx = null;
    try {
      // Create some data and persist it
      tx = session.beginTransaction();

      Track track = new Track("Russian Trance",
                              new HashSet<Artist>(), 1
                              new Date(), (short)0);
      addTrackArtist(track, getArtist("PPK", true, session));

      track = new Track("Video Killed the Radio Star",
                        Time.valueOf("00:03:49"), new HashSet<Artist>(),
                        new Date(), (short)0);
      addTrackArtist(track, getArtist("The Buggles", true, session));
      track = new Track("Gravity's Angel",
                        Time.valueOf("00:06:06"), new HashSet<Artist>(),
                        new Date(), (short)0);
     addTrackArtist(track, getArtist("Laurie Anderson", true, session));

     track = new Track("Adagio for Strings (Ferry Corsten Remix)", 2
                       Time.valueOf("00:06:35"), new HashSet<Artist>(),
                       new Date(), (short)0);
     addTrackArtist(track, getArtist("William Orbit", true, session));
     addTrackArtist(track, getArtist("Ferry Corsten", true, session));
     addTrackArtist(track, getArtist("Samuel Barber", true, session));

     track = new Track("Adagio for Strings (ATB Remix)",
                       Time.valueOf("00:07:39"), new HashSet<Artist>(),
                       new Date(), (short)0);
     addTrackArtist(track, getArtist("William Orbit", true, session));
     addTrackArtist(track, getArtist("ATB", true, session));
     addTrackArtist(track, getArtist("Samuel Barber", true, session));

     track = new Track("The World '99",
                       Time.valueOf("00:07:05"), new HashSet<Artist>(),
                       new Date(), (short)0);
     addTrackArtist(track, getArtist("Pulp Victim", true, session));
     addTrackArtist(track, getArtist("Ferry Corsten", true, session));

     track = new Track("Test Tone 1", 3
                       Time.valueOf("00:00:10"), new HashSet<Artist>(),
                       new Date(), (short)0);

     // We're done; make our changes permanent

    } catch (Exception e) {
      if (tx != null) {
        // Something went wrong; discard all partial changes
      throw new Exception("Transaction failed", e);
    } finally {
      // No matter what, close the session

    // Clean up after ourselves

The changes to the existing code are pretty minimal:


The lines that created the three tracks from Chapter 3 need only a single new parameter each to supply an initially empty set of Artist associations. Each also gets a new follow-up line establishing an association to the artist for that track. We could have structured this code differently, by writing a helper method to create the initial HashSet containing the artist, so we could do this all in one line. The approach we actually used scales better to multiartist tracks, as the next section illustrates.


The largest chunk of new code simply adds three new tracks to show how multiple artists per track are handled. If you like electronica and dance remixes (or classical for that matter), you know how important an issue that can be. Because we set the links up as collections, it’s simply a matter of adding each artist link to the tracks.


Finally, we add a track with no artist associations to see how that behaves. Now you can run ant ctest to create the new sample data containing tracks, artists, and associations between them.


If you’re making changes to your test data creation program and you want to try it again starting from an empty database, issue the command ant schema ctest. This useful trick tells Ant to run the schema and ctest targets one after the other. Running schema blows away any existing data; then ctest gets to create it anew.


Of course, in real life you’d be getting this data into the database in some other way—through a user interface, or as part of the process of importing the actual music. But your unit tests might look like this.

What just happened?

There’s no visible output from running ctest beyond the SQL statements Hibernate is using (if you still have show_sql set to true in hibernate.cfg.xml) and those aren’t very informative; look at data/music.script to see what got created or fire up ant db to look at it via the graphical interface. Take a look at the contents of the three tables. Figure 4-2 shows what ended up in the join table that represents associations between artists and tracks. The raw data is becoming cryptic. If you’re used to relational modeling, this query shows you everything worked. If you’re mortal like me, the next section is more convincing; it’s certainly more fun.

Artist and track associations created by the new version of CreateTest

Figure 4-2. Artist and track associations created by the new version of CreateTest

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, interactive tutorials, and more.

Start Free Trial

No credit card required