5.4 Lifecycle Associations
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.
NOTE
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:
session.save(track);
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!
|
|
|