4.2 Persisting Collections


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

4.2.1 How do I do that?

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

Example 4-5. Utility methods to help find and create artists, and to link them to tracks
 1 package com.oreilly.hh;  2  3 import net.sf.hibernate.*;  4  5 import net.sf.hibernate.cfg.Configuration;  6  7 import java.sql.Time;  8 import java.util.  *  ;  9 10 /** 11  * Create  more  sample data, letting Hibernate persist it for us. 12  */ 13 public class CreateTest { 14 15  /**  16  * Look up an artist record given a name.  17  * @param name the name of the artist desired.  18  * @param create controls whether a new record should be created if  19  * the specified artist is not yet in the database.  20  * @param session the Hibernate session that can retrieve data  21  * @return the artist with the specified name, or <code>null</code> if no  22  * such artist exists and <code>create</code> is <code>false</code>.  23  * @throws HibernateException if there is a problem.  24  */  25  public static Artist getArtist(String name, boolean create,  26  Session session)  27  throws HibernateException  28  {  29  Query query = session.getNamedQuery(  30  "com.oreilly.hh.artistByName");  31  query.setString("name", name);  32  Artist found = (Artist)query.uniqueResult();  33  if (found == null && create) {  34  found = new Artist(name, new HashSet());  35  session.save(found);  36  }  37  return found;  38  }  39 40  /**  41  * Utility method to associate an artist with a track  42  */  43  private static void addTrackArtist(Track track, Artist artist) {  44  track.getArtists().add(artist);  45  }  

As is so often the case when working with Hibernate, this code is pretty simple and self explanatory. (Do notice that line 8 has changed ”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 (starting at line 15) does the work of looking them up by name. The uniqueResult() method it uses on line 32 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'd be thrown an exception if there were more than one result, but our unique constraint on the column will prevent that.)

So all we need to do is check for null on line 33, and create a new Artist (lines 34-35) if we didn't find one and 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.

The addTrackArtist() method (lines 40-45) 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 objectrelational mapping tools!

You might have noticed that getArtist() uses a named query to retrieve the Artist record. In Example 4-6, 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-6. Artist lookup query to be added to the artist mapping document
 <query name="com.oreilly.hh.artistByName">     <![CDATA[         from com.oreilly.hh.Artist as artist         where upper(artist.name) = upper(:name)       ]]> </query> 

We use the upper() function to perform 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 caseinsensitive 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.

Now we can use this infrastructure to actually create some tracks with linked artists. Example 4-7 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-7. Revisions to main method of CreateTest.java in order to add artist associations
 1 public static void main(String args[]) throws Exception {  2 // Create a configuration based on the properties file we've put  3 // in the standard place.  4 Configuration config = new Configuration();  5  6 // Tell it about the classes we want mapped, taking advantage of  7 // the way we've named their mapping documents.  8 config.addClass(Track.class)  .addClass(Artist.class);  9 10 // Get the session factory we can use for persistence 11 SessionFactory sessionFactory = config.buildSessionFactory(); 12 13 // Ask for a session using the JDBC information we've configured 14 Session session = sessionFactory.openSession(); 15Transaction tx = null; 16 try { 17     // Create some data and persist it 18     tx = session.beginTransaction(); 19 20     Track track = new Track("Russian Trance", 21     "vol2/album610/track02.mp3", 22     Time.valueOf("00:03:30"), new Date(), 23     (short)0  , new HashSet());  24  addTrackArtist(track, getArtist("PPK", true, session));  25     session.save(track); 26 27     track = new Track("Video Killed the Radio Star", 28       "vol2/album611/track12.mp3", 29       Time.valueOf("00:03:49"), new Date(), 30       (short)0  , new HashSet());  31  addTrackArtist(track, getArtist("The Buggles", true, session));  32     session.save(track); 33 34 35     track = new Track("Gravity's Angel", 36               "vol2/album175/track03.mp3", 37       Time.valueOf("00:06:06"), new Date(), 38       (short)0  , new HashSet());  39  addTrackArtist(track, getArtist("Laurie Anderson", true, session));  40     session.save(track); 41 42  track = new Track("Adagio for Strings (Ferry Corsten Remix)",  43  "vol2/album972/track01.mp3",  44  Time.valueOf("00:06:35"), new Date(),  45  (short)0, new HashSet());  46  addTrackArtist(track, getArtist("William Orbit", true, session));  47  addTrackArtist(track, getArtist("Ferry Corsten", true, session));  48  addTrackArtist(track, getArtist("Samuel Barber", true, session));  49  session.save(track);  50 51  track = new Track("Adagio for Strings (ATB Remix)",  52  "vol2/album972/track02.mp3",  53  Time.valueOf("00:07:39"), new Date(),  54  (short)0, new HashSet());  55  addTrackArtist(track, getArtist("William Orbit", true, session));  56  addTrackArtist(track, getArtist("ATB", true, session));  57  addTrackArtist(track, getArtist("Samuel Barber", true, session));  58  session.save(track);  59 60  track = new Track("The World '99",  61  "vol2/singles/pvw99.mp3",  62  Time.valueOf("00:07:05"), new Date(),  63  (short)0, new HashSet());  64  addTrackArtist(track, getArtist("Pulp Victim", true, session));  65  addTrackArtist(track, getArtist("Ferry Corsten", true, session));  66  session.save(track);  67 68  track = new Track("Test Tone 1",  69  "vol2/singles/test01.mp3",  70  Time.valueOf("00:00:10"), new Date(),  71  (short)0, new HashSet());  72  session.save(track);  73 74    // We're done; make our changes permanent 75    tx.commit(); 76 77} catch (Exception e) { 78    if (tx != null) { 79 // Something went wrong; discard all partial changes 80 tx.rollback(); 81    } 82    throw e; 83} finally { 84    // No matter what, close the session 85    session.close(); 86} 87 88// Clean up after ourselves 89sessionFactory.close(); 90  } 91 } 

The changes to the existing code are pretty minimal. First we need to map our new Artist class, which takes just one method call on line 8 (again, thanks to the naming convention we've been following to link our mapping documents to their classes). 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 (lines 23, 30, and 38). 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 multi-artist tracks, as the next section illustrates.

The largest chunk of new code, lines 42-66, 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, lines 68-72 add a track with no artist associations to see how that behaves, too. Now you can run ant ctest to create the new sample data containing tracks, artists, and associations between them.

A useful trick if you're making changes to your test data creation program and you want to try it again starting from an empty database is to issue the command ant schema ctest . This 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

4.2.2 What just happened ?

There's no visible output from running ctest : 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.

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

Hibernate. A Developer's Notebook
Hibernate: A Developers Notebook
ISBN: 0596006969
EAN: 2147483647
Year: 2003
Pages: 65
Authors: James Elliott

Similar book on Amazon

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net