4.4 Using Bidirectional Associations

     

In our creation code, we established links from tracks to artists , simply by adding Java objects to appropriate collections. Hibernate did the work of translating these associations and groupings into the necessary cryptic entries in a join table it created for that purpose. It allowed us with easy, readable code to establish and probe these relationships. But remember that we made this association bidirectional ”the Artist class has a collection of Track associations too. We didn't bother to store anything in there.

The great news is that we don't have to. Because of the fact that we marked this as an inverse mapping in the Artist mapping document, Hibernate understands that when we add an Artist association to a Track , we're implicitly adding that Track as an association to the Artist at the same time.

This convenience works only when you make changes to the 'primary' mapping, in which case they propagate to the inverse mapping. If you make changes only to the inverse mapping, in our case the Set of tracks in the Artist object, they will not be persisted . This unfortunately means your code must be sensitive to which mapping is the inverse.


Let's build a simple interactive graphical application that can help us check whether the artist to track links really show up. It will let you type in an artist's name , and show you all the tracks associated with that artist. A lot of the code is very similar to our first query test. Create the file QueryTest2.java and enter the code shown in Example 4-10.

Example 4-10. Source for QueryTest2.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 import java.awt.*;  9 import java.awt.event.*; 10 import javax.swing.*; 11 12 /** 13  * Provide a user interface to enter artist names and see their tracks. 14  */ 15 public class QueryTest2 extends JPanel { 16 17     JList list; // Will contain tracks associated with current artist 18     DefaultListModel model; // Lets us manipulate the list contents 19 20     /** 21      * Build the panel containing UI elements 22      */ 23     public QueryTest2() { 24         setLayout(new BorderLayout()); 25         model = new DefaultListModel(); 26         list = new JList(model); 27         add(new JScrollPane(list), BorderLayout.SOUTH); 28 29         final JTextField artistField = new JTextField(30); 30         artistField.addKeyListener(new KeyAdapter() { 31                 public void keyTyped(KeyEvent e) { 32        SwingUtilities.invokeLater(new Runnable() { 33 public void run() { 34     updateTracks(artistField.getText()); 35 } 36  }); 37    } 38       }); 39         add(artistField, BorderLayout.EAST); 40         add(new JLabel("Artist: "), BorderLayout.WEST); 41    } 42 43    /** 44     * Update the list to contain the tracks associated with an artist 45     */ 46    private void updateTracks(String name) { 47        model.removeAllElements(); // Clear out previous tracks 48        if (name.length() < 1) return; // Nothing to do 49        try { 50             // Ask for a session using the JDBC information we've configured 51             Session session = sessionFactory.openSession(); 52             try { 53                 Artist artist = CreateTest.getArtist(name, false, session); 54                 if (artist == null) { // Unknown artist 55                    model.addElement("Artist not found"); 56                    return; 57                 } 58                 // List the tracks associated with the artist 59                 for (Iterator iter = artist.getTracks().iterator() ; 60                      iter.hasNext() ; ) { 61                     Track aTrack = (Track)iter.next(); 62                     model.addElement("Track: \"" + aTrack.getTitle() + 63                                                 "\", " + aTrack.getPlayTime()); 64                 } 65             } finally { 66                 // No matter what, close the session 67                 session.close(); 68             } 69        } catch (Exception e) { 70             System.err.println("Problem updating tracks:" + e); 71             e.printStackTrace(); 72        } 73    } 74 75    private static SessionFactory sessionFactory; // Used to talk to Hibernate 76 77    /** 78     * Set up Hibernate, then build and display the user interface. 79     */ 80        public static void main(String args[]) throws Exception { 81             // Load configuration properties, read mappings for persistent classes. 82             Configuration config = new Configuration(); 83             config.addClass(Track.class).addClass(Artist.class); 84 85             // Get the session factory we can use for persistence 86             sessionFactory = config.buildSessionFactory(); 87 88             // Set up the UI 89             JFrame frame = new JFrame("Artist Track Lookup"); 90             frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 91             frame.setContentPane(new QueryTest2()); 92             frame.setSize(400, 180); 93             frame.setVisible(true); 94        } 95    } 

The bulk of the novel code in this example deals with setting up a Swing user interface. It's actually a rather primitive interface, and won't resize nicely , but dealing with such details would make the code larger, and really falls outside the scope of this book. If you want examples of how to build rich, quality Swing interfaces, check out our Java Swing , Second Edition (O'Reilly). It's much thicker so it has room for all that good stuff.

NOTE

Yes, this is a shameless plug.

The only item I want to highlight in the constructor is the KeyListener that gets added to artistField (lines 30-38). This rather tricky bit of code creates an anonymous class whose keyTyped() method is invoked whenever the user types in the artist text field. That method, lines 31- 37, tries to update the track display by checking whether the field now contains a recognized artist name. Unfortunately, at the time the method gets invoked, the text field has not yet been updated to reflect the latest keystroke, so we're forced to defer the actual display update to a second anonymous class (the Runnable instance created on lines 32-36) via the invokeLater() method of SwingUtilities . This technique causes the update to happen when Swing 'gets around to it,' which in our case means the text field will have finished updating itself.

The updateTracks() method that gets called at that point is where the interesting Hibernate stuff happens. It starts by clearing the list on line 47, discarding any tracks it might have previously been displaying. If the artist name is empty, that's all it does. Otherwise, it opens a Hibernate session on line 51 and tries to look up the artist using the getArtist() method we wrote in CreateTest . This time we tell it not to create an artist if it can't find the one we asked for, so we'll get back a null if the user hasn't typed the name of a known artist. If that's the case, we just display a message to that effect (line 55).

If we do find an Artist record, on the other hand, line 59 iterates over any Track records found in the artist's set of associated tracks, and lines 61-63 display information about each one. All this will test whether the inverse association has worked the way we'd like it to. Finally (no pun intended), lines 65-68 make sure to close the session when we're leaving the method, even through an exception. You don't want to leak sessions ”that's a good way to bog down and crash your whole database environment.

The main() method starts out with the same Hibernate configuration steps we've seen before in lines 81-86, then creates and displays the user interface frame in lines 89-93. Line 90 sets the interface up to end the program when it's closed. After displaying the frame, main() returns. From that point on, the Swing event loop is in control.

Once you've created (or downloaded) this source file, you also need to add a new target, shown in Example 4-11, to the end of build.xml (the Ant build file) to invoke this new class.

Example 4-11. Ant target for running the new query test
 <target name="qtest2" description="Run a simple Artist exploration GUI"         depends="compile">   <java classname="com.oreilly.hh.QueryTest2" fork="true">     <classpath refid="project.class.path"/>   </java> </target> 

NOTE

This is very similar to the existing 'qtest' target; copy and tweak that.

Now you can fire it up by typing ant qtest2 and play with it for yourself. Figure 4-3 shows the program in action, displaying tracks for one of the artists in our sample data.

Figure 4-3. A simple artist tracks browser
figs/hibernate_0403.jpg



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

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