Data objects in a DAO architecture

by Keld H. Hansen

Introduction

My recent article, "Managing DAO transactions in Java", which was a follow-up to another article, "Using DAOs in Apache Struts", described how to use transactions with the DAO design pattern in simple Java applications. Several layers were identified, which can be illustrated by this figure:

Clients - can be any Java module using business logic, e.g.Struts Action classes.
Transaction Handlers - the interface modules to the clients and the DAOs. They contain business logic and handle transactions (when needed)
DAOs - are a thin layer to the backend. A DAO's API must not reveal anything about how the backend actually works.
Data - shows the data being communicated between the layers. The format and characteristics of these data are the topics of the current article.

The roles of the Transaction handlers and DAOs, which are very important for the following discussions, are described in details in the articles mentioned above.

The DAO layer

If the backend is a database system, a very simple DAO solution is to make a DAO for each table in the database. Each DAO will therefore need a bean with member variables that correspond 1-to-1 to the columns in a table. The names and types of the member variables do not have to be the same as the columns. Choose names and types that are useful for the Java application being built.

We will use the name DAO bean for the data object used by the DAOs.

With this simple setup it's actually possible to generate the part of the DAOs and all of their beans directly from the database table definitions. It's not too hard to code this yourself; most of the database information can be taken from the "metadata" available through the java.sql package.

Referring to the article "Using DAOs in Apache Struts" we will assume we're working with a database table "DVD" with two "varchar" columns called "id" and "title". A bean that matches this table is this:

package dk.hansen;

public class DVD {

  private String id;
  private String title;

  public DVD(String id, String title) {
    setId(id);
    setTitle(title);
  }

  public String getId() {return id;}

  public void setId(String id) {this.id = id;}

  public String getTitle() {return title;}

  public void setTitle(String title) {this.title = title;}

}

The DAO itself will typically have methods like these:

Name Purpose
void create(DVD dvd) Create a DVD in the database
DVD find(String id) Locate a DVD by its key
void update(DVD dvd) Update an existing DVD
void update(String id, String title) Update a specific DVD with a new title

As can be seen the DVD bean is used as the transport class to and from the DAO layer. Note also, that a method like "update" could have several forms, depending on the actual needs from the clients. Often it is most convenient to give the complete DAO bean as input to a method. But if only a few data values should be changed, then a method like the last shown for update could be useful. 

It's perfectly valid to use other beans for data transport. If a table contains many columns, it might be an idea to have a "slimmer" bean carrying the most important information. We'll see more to this below.

Database views

Data is often read from a database using a "view". A view is a SELECT statement joining several tables most often using a WHERE clause. It's absolutely legal to make a DAO that maps a view. Since a view can not be used for updating a database, such a DAO will only have finder-methods.

The Transaction Handler layer

Having determined to use "DAO beans" to communicate with the DAO layer, it's now time to see how clients should communicate with the Transaction Handler layer. First of all, the API offered by the handlers should be useful to the clients. This means that method parameters should match the data the clients have at hand. Clients could be browser/Swing applications, or web services, but they could also be other transaction handlers. Therefore expect the handlers to have several APIs available.

It's common to call the data classes used by transactions handlers for Value Objects (VOs) or Data Transfer Objects (DTOs). These names are used somewhat ad-hoc, but in general these classes act as wrapper classes for data being used by the business logic. In this article I'll use the term DTO for data classes used by the handlers.

So, the picture now looks like this:

A DTO is often a mixture of data returned from more than one DAO bean, i.e. data held in several tables on the database. If we expand our "DVD business case" with a "Director" table, then a client demand might be to fetch DVD information including director data (name, birthday, other movies etc.). But other clients might only want the title of a DVD, and it's not difficult to imagine many client requests that differ only slightly in the amount of data they want returned.

An example: if the DVD table contains a key to the Director table, should we then return only the key or some or all of the director data? And if the Director table has keys to other tables, when should we stop retrieving and returning data to the clients?

If you're not careful you may end up with a lot of DTOs, all having a lot of properties in common, but without proper inheritance. There is no single, simple answer to how to cut your DTOs correctly, but some guidelines may be given.

Using the same data classes

You may be tempted to use the same data classes as transport to the Handlers and the DAOs. In simpler applications, or when handling simple requests, the clients want exactly what's in the DAO beans. The downside by doing this is that changes in a DAO, and thus also in the DAO bean, will be visible to the clients. So to avoid tight bindings between the layers, only use the DAO beans as the interface to the DAOs.

You may have noted, that in my previous articles about the DAO pattern, DAO beans were actually also used as DTOs. This was done to keep the focus on the application layers, and not on the data transport.

Returning only the the key

Some clients may only want the database key returned. It's therefore a good idea to define a Key class for the DTOs--and also for the DAO beans--and let all DTOs use this class (assuming for simplicity that all DTOs use the same key type):

package dk.hansen.dto;

public class Key_DTO {

  private String id;

  public Key_DTO() {}

  public Key_DTO(String id) {
    this.id =id;
  }

  public String getId() {return id;}

  public void setId(String id) {this.id = id;}
}

Using the Key

The DTO with DVD information may now use Key_DTO:

package dk.hansen.dto;

public class DVDKey_DTO {

  private Key_DTO key;

  public Key_DTO getKey() {return key;}

  public void setKey(Key_DTO key) {this.key = key;}
. . .

The smallest possible DVD DTO contains only one property besides the key:

package dk.hansen.dto;

public class DVDKeyValue_DTO extends DVDKey_DTO {

  private String title;

  public String getTitle() {return title;}

  public void setTitle(String title) {this.title = title;}

  public static void main(String[] args) {
    DVDKeyValue_DTO dvd = new DVDKeyValue_DTO();
    dvd.setKey(new Key_DTO("12"));
    dvd.setTitle("Jurassic Park");
  }
}

The main method merely illustrates how a handler could use the DTO.

As long as you can extend a class into another, everything is fine. But of course you can imagine a DVD DTO with any combination of properties from the DVD bean, and you'd never make a DTO for every combination. What often is useful is to create

- a DTO with only a key
- a DTO with the key and an important property, like the title of the DVD (often used in GUI drop downs)
- a DTO with all the properties from the DVD

Use the above scheme to make these classes.

Relations between tables

As mentioned above, the DVD might have a relation to a Director table, holding information about the director of the movie on the DVD. This is a situation where we need two DAOs - one for the DVD and one for the Director - and a DTO that combines the data from these two tables. It's then the job for the Transaction Handler to get the information from the DAOs and return a DTO to the clients.

The problem is when some clients want only the key of the director, and other wants more information about the director. Either you may end up with a single DTO containing all director information (and possibly more), or a lot of nearly identical DTOs. An approach that can be used is this: First define a DVD DTO that only holds the key for the director, and then make an extension of this that holds the full director information. An example will show how it works. First the Director DTO:

package dk.hansen.dto;

import java.util.Date;

public class Director_DTO {

  private Key_DTO key;

  private String name;

  ... (other attributes, constructors, setters and getters)
}

Now it's possible to use this class in a new DVD DTO: 

package dk.hansen.dto;

public class DVD_DTO extends DVDKeyValue_DTO {

  protected Director_DTO director = new Director_DTO();

  public Key_DTO getDirectorKey() {
    return director.getKey();
  }

  public void setDirectorKey(Key_DTO directorKey) {
    director.setKey(directorKey);
  }

  public static void main(String[] args) {
    // For testing only...
    Director_DTO dir = new Director_DTO();
    dir.setKey(new Key_DTO("13"));
    dir.setName("Steven Spielberg");
 
    DVD_DTO dvd = new DVD_DTO();
    dvd.setKey(new Key_DTO("12"));
    dvd.setTitle("Jurassic Park");
    dvd.setDirectorKey(dir.getKey());
    
    System.out.println(dvd.getDirectorKey().getId());
  }
}

Note that the "director" member variable is actually defined as a complete Director DTO object, but you can only get to its key, since its getter- and setter-methods only references the key. To get a DVD DTO that contains a complete Director object you extend the class: 

package dk.hansen.dto;

public class DVDWithDirector_DTO extends DVD_DTO {

  public void setDirector(Director_DTO director) {
    this.director = director;
  }

  public Director_DTO getDirector() {
    return director;
  }

  public static void main(String[] args) {
    Director_DTO dir = new Director_DTO();
    dir.setKey(new Key_DTO("13"));
    dir.setName("Steven Spielberg");

    DVDWithDirector_DTO dvd = new DVDWithDirector_DTO();
    dvd.setKey(new Key_DTO("123"));
    dvd.setTitle("Some Movie");
    dvd.setDirector(dir);

    System.out.println(dvd.getDirectorKey().getId());
    System.out.println(dvd.getDirector().getKey().getId());
    System.out.println(dvd.getDirector().getName());
  }
}

Using the DTOs in the Transaction Handlers

Now to show how to code methods in the Transaction Handler that returns either a DVD with only the key of the director or a complete director. First the simple case--here we only need to call the DVD DAO to get the result:

public DVD_DTO findDVD(String dvdId) throws DAOException {
  DVD_DTO dto = null;

  DAOFactoryIF factory = new JdbcDAOFactory();
  DVDDAOIF dvdDao      = factory.createDVDDAO();
  dvdDao.setTransaction(trans);
  DVD dvd = null;

  try {
    dvd = dvdDao.findDVD(dvdId);
  } catch (DAOException e) {
    String msg = "findDVD failed for id=" + dvdId;
    logger.error(msg);
    throw new DAOException(msg, e);
  }

  if (dvd != null) {
    dto = new DVD_DTO();
    dto.setKey(ConvertKey.toDTO(dvd.getKey()));
    dto.setTitle(dvd.getTitle());
    dto.setDirectorKey(ConvertKey.toDTO(dvd.getDirector()));
  }
  
  return dto;
}

Even if the keys used by the DAO beans and the DTOs are often the same, they don't have to be. Hence the utility "ConvertKey":

package dk.hansen.dto;

import dk.hansen.dao.bean.Key;

public class ConvertKey {

  public static Key_DTO toDTO(Key key) {
    return new Key_DTO(key.getId());
  }

  public static Key toBean(Key_DTO key) {
    return new Key(key.getId());
  }
}

Conversion from a DAO bean to a DTO is often trivial, since they may have the same attributes. It's a good idea to place the conversion in two utility methods allowing conversions from a DAO bean to a DTO and vice versa.

The Transaction Handler code for the complete DVD:

public DVDWithDirector_DTO findDVDWithDirector(String dvdId) 
    throws DAOException {
  DVDWithDirector_DTO dvdDTO = null;

  DAOFactoryIF factory      = new JdbcDAOFactory();
  DVDDAOIF dvdDao           = factory.createDVDDAO();
  DirectorDAOIF directorDao = factory.createDirectorDAO();
  dvdDao.setTransaction(trans);
  DVD dvd;

  try {
    dvd = dvdDao.findDVD(dvdId);
  } catch (DAOException e) {
    String msg = "getDVD failed for id=" + dvdId;
    logger.error(msg);
    throw new DAOException(msg, e);
  }

  if (dvd != null) {
    directorDao.setTransaction(trans);
    Director director;
    try {
      director = directorDao.getDirector(dvd.getDirector().getId());
    } catch (DAOException e) {
      String msg = "getDVD failed for directorid=" + 
                   dvd.getDirector().getId();
      logger.error(msg);
      throw new DAOException(msg, e);
    }

    Director_DTO directorDTO = new Director_DTO();
    directorDTO.setKey(ConvertKey.toDTO(director.getKey()));
    directorDTO.setName(director.getName());

    dvdDTO = new DVDWithDirector_DTO();
    dvdDTO.setKey(ConvertKey.toDTO(dvd.getKey()));
    dvdDTO.setTitle(dvd.getTitle());
    dvdDTO.setDirector(directorDTO);
  }
  return dvdDTO;
}

Validation of DAO beans and DTOs

Should the DAOs or the Transaction Managers validate the data they receive in these classes? As always, it is dependent on the application at hand. To look at the Transaction Managers first, then one must know who the clients are. If the clients are part of the same project as the Handlers, then a design criterion could be to let the clients have the responsibility for producing valid input to the handlers. If the application is "mission-critical", then you might want to add validation in the handlers as well. Another possibility is to let the handlers offer two interfaces--one with validation, one without.

The same considerations could be made for the DAOs. If the clients of the DAOs are the handlers, then they might have the responsibility for producing valid input to the DAOs. Since DAOs write data to the backend, it's important to be sure that data is valid, or you might end up with a corrupt database.

The importance of making the right design

Making the correct data classes in a DAO application--or in any application--is by no means trivial. It's worthwhile to use time for this design, since development and maintenance of the application will be made much easier with a well considered and properly documented design. I hope that the ideas presented above may guide you to the right design for your application.

Happy coding!

Other resources