Dependent Mapping Pattern

This pattern belongs to Object-Relational Structural Patterns Catalog and this Catalog belongs to Patterns of Enterprise Application Architecture.

Intent

Has one class perform the database mapping for a child class.

Explanation

Some objects naturally appear in the context of other objects. Tracks on an album may be loaded or saved whenever the underlying album is loaded or saved. If they aren’t referenced to by any other table in the database, you can simplify the mapping procedure by having the album mapper perform the mapping for the tracks as well—treating this mapping as a dependent mapping.
Let's take one more example, Person can have multiple addresses like permanent address, temporary address etc. So according to database mapping Person is the parent object addresses are child object.

When person object is saved to the database then it's dependent child objects also saved to the database.

How It Works


The basic idea behind Dependent Mapping is that one class (the dependent) relies upon some other class (the owner) for its database persistence. Each dependent can have only one owner and must have one owner.

A dependent may itself be the owner of another dependent. In this case the owner of the first dependent is also responsible for the persistence of the second dependent. You can have a whole hierarchy of dependents controlled by a single primary owner.

Writing and saving of dependents is left to the owner, and there are no outside references, updates to the dependents can be handled through deletion and insertion. Thus, if you want to update the collection of dependents you can safely delete all rows that link to the owner and then reinsert all the dependents.

When to Use It

You use Dependent Mapping when you have an object that’s only referred to by one other object, which usually occurs when one object has a collection of dependents. Dependent Mapping is a good way of dealing with the awkward situation where the owner has a collection of references to its dependents but there’s no back pointer. Providing that the many objects don’t need their own identity, using Dependent Mapping makes it easier to manage their persistence.
For Dependent Mapping to work there are a number of preconditions.
  • A dependent must have exactly one owner.
  • There must be no references from any object other than the owner to the dependent.

Implementation

Example: Albums and Tracks (Java)
Here album is domain model holds a collection of tracks. This uselessly simple application doesn’t need anything else to refer to a track, so it’s an obvious candidate for Dependent Mapping.
Whenever album object is persisted in the database then it's dependent objects that is tracks also persisted into the database.
Let's have a sample source code for Dependent Mapping Pattern.

class Track {

   private final String title;
   public Track(String title) {
      this.title = title;
   }
   public String getTitle() {
      return title;
   }
}
The tracks are held in the album class.
class Album {.

   private List tracks = new ArrayList();
   public void addTrack(Track arg) {
      tracks.add(arg);
   }
   public void removeTrack(Track arg) {
      tracks.remove(arg);
   };
   public void removeTrack(int i) {
      tracks.remove(i);
   }
   public Track[] getTracks() {
      return (Track[]) tracks.toArray(new Track[tracks.size()]);
   }
}
The album mapper class handles all the SQL for tracks and thus defines the SQL statements that access the tracks table.
class AlbumMapper {

   protected String findStatement() {
      return
         "SELECT ID, a.title, t.title as trackTitle" +
         "  FROM albums a, tracks t" +
         "  WHERE a.ID = ? AND t.albumID = a.ID" +
         "  ORDER BY t.seq";
   }
}
The tracks are loaded into the album whenever the album is loaded.
class AlbumMapper {

   protected DomainObject doLoad(Long id, ResultSet rs) throws SQLException {
      String title = rs.getString(2);
      Album result = new Album(id, title);
      loadTracks(result, rs);
      return result;
   }
   public void loadTracks(Album arg, ResultSet rs) throws SQLException {
      arg.addTrack(newTrack(rs));
      while (rs.next()) {
         arg.addTrack(newTrack(rs));
      }
   }
   private Track newTrack(ResultSet rs) throws SQLException {
      String title = rs.getString(3);
      Track newTrack = new Track  (title);
      return newTrack;
   }
}
When the album is updated all the tracks are deleted and reinserted.
class AlbumMapper {

   public void update(DomainObject arg) {
      PreparedStatement updateStatement = null;
      try {
         updateStatement = DB.prepare("UPDATE albums SET title = ? WHERE id = ?");
         updateStatement.setLong(2, arg.getID().longValue());
         Album album = (Album) arg;
         updateStatement.setString(1, album.getTitle());
         updateStatement.execute();
         updateTracks(album);
      } catch (SQLException e) {
         throw new ApplicationException(e);
      } finally {DB.cleanUp(updateStatement);
      }
   }
   public void updateTracks(Album arg) throws SQLException {
      PreparedStatement deleteTracksStatement = null;
      try {
         deleteTracksStatement = DB.prepare("DELETE from tracks WHERE albumID = ?");
         deleteTracksStatement.setLong(1, arg.getID().longValue());
         deleteTracksStatement.execute();
         for (int i = 0; i < arg.getTracks().length;  i++) {
            Track track = arg.getTracks()[i];
            insertTrack(track, i + 1, arg);
         }
      } finally {DB.cleanUp(deleteTracksStatement);
      }
   }
   public void insertTrack(Track track, int seq, Album album) throws SQLException {
      PreparedStatement insertTracksStatement = null;
      try {
         insertTracksStatement =
               DB.prepare("INSERT INTO tracks (seq, albumID, title) VALUES (?, ?, ?)");
         insertTracksStatement.setInt(1, seq);
         insertTracksStatement.setLong(2, album.getID().longValue());
         insertTracksStatement.setString(3, track.getTitle());
         insertTracksStatement.execute();
      } finally {DB.cleanUp(insertTracksStatement);
      }
   }
}

References

Comments