Hibernate is completely responsible for managing the ALBUM_TRACKS table, adding and deleting rows (and, if necessary, renumbering POSITION values) as entries are added to or removed from Album beans' tracks properties. You can test this by writing a test program to delete the second track from our test album and see the result. A very quick and dirty way to do this would be to add the following four lines (see Example 5-11) right after the existing tx.commit() line in Example 5-7 and then run ant schema ctest atest db .
Example 5-11. Deleting our album's second track
tx = session.beginTransaction(); album.getTracks().remove(1); session.update(album); tx.commit();
Doing so changes the contents of ALBUM_TRACKS as shown in Figure 5-4 (compare this with the original contents in Figure 5-3). The second record has been removed (remember that Java list elements are indexed starting with zero), and POSITION has been adjusted so that it retains its consecutive nature, corresponding to the indices of the list elements (the values you'd use when calling tracks.get() ).
Figure 5-4. Album track associations after deleting our album's second track
This happens because Hibernate understands that this list is 'owned' by the Album record, and that the 'lifecycles' of the two objects are intimately connected. This notion of lifecycle becomes more clear if you consider what happens if the entire Album is deleted: all of the associated records in ALBUM_TRACKS will be deleted as well. (Go ahead and modify the test program to try this if you're not convinced.)
Contrast this with the relationship between the ALBUM table and the TRACK table. Tracks are sometimes associated with albums, but they are sometimes independent. Removing a track from the list got rid of a row in ALBUM_TRACKS , eliminating the link between the album and track, but didn't get rid of the row in TRACK , so it didn't delete the persistent Track object itself. Similarly, deleting the Album would eliminate all the associations in the collection, but none of the actual Tracks . It's the responsibility of our code to take care of that when appropriate (probably after consulting the user , in case any of the track records might be shared across multiple albums, as discussed above).
If we don't need the flexibility of sharing the same track between albums ”disk space is pretty cheap lately given the size of compressed audio ”we can let Hibernate manage the TRACK records for the album in the same way it does the ALBUM_TRACKS collection. It won't assume it should do this, because Track and Album objects can exist independently, but we can establish a lifecycle relationship between them in the album mapping document.
By now you're probably not surprised there's a way to automate this.
5.4.1 How do I do that?
Example 5-12 shows (in bold) the changes we'd make to the tracks property mapping in Album.hbm.xml .
Example 5-12. Establishing a lifecycle relationship between an album and its tracks
<list name="tracks" table="ALBUM_TRACKS" cascade="all" > <meta attribute="use-in-tostring">true</meta> <key column="ALBUM_ID"/> <index column="POSITION"/> <composite-element class="com.oreilly.hh.AlbumTrack"> <many-to-one name="track" class="com.oreilly.hh.Track" cascade="all" > <meta attribute="use-in-tostring">true</meta> <column name="TRACK_ID"/> </many-to-one> <property name="disc" type="integer"/> <property name="positionOnDisc" type="integer"/> </composite-element> </list>
The cascade attribute tells Hibernate that you want operations performed on a 'parent' object to be transitively applied to its 'child' or 'dependent' objects. It's applicable to all forms of collections and associations. There are several possible values to choose among. The most common are none (the default), save-update, delete , and all (which combines save-update and delete ). You can also change the default from none to save-update throughout your entire mapping document by supplying a default-cascade attribute in the hibernate-mapping tag itself.
In our example, we want the tracks owned by an album to be automatically managed by the album, so that when we delete the album, its tracks are deleted. Note that we need to apply the cascade attribute both to the tracks collection and its constituent track element to achieve this. Also, by using a cascade value of all , we eliminate the need to explicitly save any Track objects we create for the album ”the addAlbumTrack() method of Example 5-7 no longer needs the line:
By telling Hibernate that it's fully responsible for the relationship between an album and its track, we enable it to persist tracks when they're added to the album as well as delete them when the album itself is deleted.
Delegating this sort of bookkeeping to the mapping layer can be very convenient , freeing you to focus on more abstract and important tasks , so it is worth using when appropriate. It's reminiscent of the liberation provided by Java's pervasive garbage collection, but it can't be as comprehensive because there is no definitive way to know when you're finished with persistent data by performing reachability analysis; you need to indicate it by calling delete() and establishing lifecycle connections. The trade-off between flexibility and simple automation is yours to make, based on the nature of your data and the needs of your project.
| || |
Hibernate's management of lifecycle relationships is not foolproof ”or perhaps it's more accurate to say it's not all-encompassing. For example, if you use Collections methods to remove a Track from an Album's tracks property, this breaks the link between the Album and Track but does not actually delete the Track record. Even if you later delete the entire Album , this Track will remain , because it wasn't linked to the Album at the time that it was deleted. Try some of these experiments by modifying AlbumTest.java appropriately and look at the resulting data in the tables!