Wednesday, February 6, 2013

Data Access Object (DAO) Design Pattern

One aspect of the business layer is the data access layer that connects the services with the database. Accessing data varies depending on the source of the data. Access to persistent data varies greatly depending on the type of storage (database, flat files, xml files, and so on) and it even differs from its implementation (for example different SQL-dialects).

Data Access Layer has proven good in separate business logic layer and persistent layer. The DAO design pattern completely  hides the data access implementation from its clients. The interfaces given to client does not changes when the underlying data source mechanism changes. this is the capability which allows the DAO to adopt different access scheme without affecting to business logic or its clients. generally it acts as a adapter between its components and database. The DAO design pattern consists of some factory classes, DAO interfaces and some DAO classes to implement those interfaces.

The goal is to abstract and encapsulate all access to the data and provide an interface. This is called the Data Access Object pattern. In a nutshell, the DAO "knows" which data source (that could be a database, a flat file or even a WebService) to connect to and is specific for this data source (e.g. a OracleDAO might use oracle-specific data types, a WebServiceDAO might parse the incoming and outgoing message etc.).

From the applications point of view, it makes no difference when it accesses a relational database or parses xml files (using a DAO). The DAO is usually able to create an instance of a data object ("to read data") and also to persist data ("to save data") to the datasource.

Applicability / Uses
Use a Data Access Object when:
• you need to access a persistent storage more than one time, especially if you want to exchange the data source later.
• you want to separate a data resource's client interface from its data access mechanisms
• you want to adapt a specific data resource's access API to a generic client interface
• in a larger project, different teams work on different parts of the application: the DAO pattern allows clean separation of concerns.

Related Patterns
Abstract Factory: Applications often use a Factory to select the right DAO implementation at run time.
Transfer Object: The DAO pattern often uses a Transfer Object to send data from the data source to its client and vice versa.

Structure
UML diagram of the sample code:


Sample
In the following example we will create a Data Access Object for saving/retrieving the data of books.
To keep the source-code simple, import declarations and exception-handling is not shown.

public interface BookDAO {
  public void saveBook(Book b);
  public Book loadBook(String isbn);
}


The first implementation flavor will use a simple database (possibly remote) for storing the data:

public class DBBookDAO implements BookDAO {
private PreparedStatement saveStmt;
private PreparedStatement loadStmt;
  public DBBookDAO(String url, String user, String pw) {
    Connection con = DriverManager.getConnection(url, user, pw);
    saveStmt = con.prepareStatement("INSERT INTO books(isbn, title, author) "
                                   +"VALUES (?, ?, ?)");
    loadStmt = con.prepareStatement("SELECT isbn, title, author FROM books "
                                   +"WHERE isbn = ?");
  }
  public Book loadBook(String isbn) {
    Book b = new Book();
    loadStmt.setString(1, isbn);
    ResultSet result = loadStmt.executeQuery();
    if (!result.next()) return null;
   
    b.setIsbn(result.getString("isbn"));
    b.setTitle(result.getString("title"));
    b.setAuthor(result.getString("author"));
    return b;
  }
  public void saveBook(Book b) {
    saveStmt.setString(1, b.getIsbn());
    saveStmt.setString(2, b.getTitle());
    saveStmt.setString(3, b.getAuthor());
    saveStmt.executeUpdate();
  }
}



The second implementation flavor saves every book in its own text-file on the local hard drive:

public class FileBookDAO implements BookDAO {
       
  private String basePath;
  public FileBookDAO(String basePath) {
    this.basePath = basePath;
  }
  public Book loadBook(String isbn) {
    FileReader fr = new FileReader(basePath + isbn);
    BufferedReader br = new BufferedReader(fr);
    Book b = new Book();
    String rIsbn = br.readLine();
    String rTitle = br.readLine();
    String rAuthor = br.readLine();
       
    if (rIsbn.startsWith("ISBN: ")) {
      b.setIsbn(rIsbn.substring("ISBN: ".length()));
    } else {
      return null;
    }
    if (rTitle.startsWith("TITLE: ")) {
      b.setTitle(rTitle.substring("TITLE: ".length()));
    } else {
      return null;
    }
    if (rAuthor.startsWith("AUTHOR: ")) {
      b.setAuthor(rAuthor.substring("AUTHOR: ".length()));
    } else {
      return null;
    }
    return b;
  }
  public void saveBook(Book b) {
    FileWriter fw = new FileWriter(basePath + b.getIsbn() + ".book");
    fw.write("ISBN: " + b.getIsbn());
    fw.write("TITLE: " + b.getTitle());
    fw.write("AUTHOR: " + b.getAuthor());
    fw.close();
  }
}


Data Access Objects (DAOs) :
  • can be used in a large percentage of applications - anywhere data storage is required.
  • hide all details of data storage from the rest of the application.
  • act as an intermediary between your application and the database. They move data back and forth between Java objects and database records.
  • allow ripple effects from possible changes to the persistence mechanism to be confined to a specific area.
For simple DAOs, various implementation styles are possible :
Style 1 - instance methods
MessageDAO dao = new MessageDAO(); List<Message> messages = dao.fetchRecentMessages();
This is the recommended style. Create an ordinary object in the usual way, and use its services.
Style 2 - variation on Style 1
List<Message> messages = new MessageDAO().fetchRecentMessages();
The DAO is created and used on the same line. This style seems a bit less legible than Style 1.
Style 3 - static methods
List<Message> messages = MessageDAO.fetchRecentMessages();
Probably the least desirable. One must exercise care that such classes are thread-safe. This issue usually doesn't exist in Styles 1 and 2, if the DAO has no static members, is short-lived, and there is no possibility of sharing data between threads.

Full DAO Design Pattern
It is common to describe Data Access Objects as an extended design pattern. That design pattern centers on allowing wholesale changes to a datastore mechanism - for example, changing from a relational database to using the file system, or some other means of data storage. It is only required when multiple storage mechanisms must coexist.

It must be stressed that most applications do not need the full DAO design pattern as usually described. Most business applications are deployed with a very particular database in mind. In such cases, allowing a deployer to configure various types of datastore seems entirely superfluous. If your application interacts with only one datastore at a time, then the full DAO pattern is clearly of dubious benefit. Rather, implementing each DAO as a single, ordinary class seems more appropriate and effective.

Minimize ripple effects
Much of object programming is centered on minimizing the ripple effects caused by changes to a program. This is done simply by keeping details secret (information hiding or encapsulation).
The principal ways of doing this are
  • indirection - named constants replacing "magic numbers", for example
  • minimizing visibility - private fields, package-private classes, for example
  • generic references (polymorphism) - using high level references (interfaces or abstract classes) instead of low level references (concrete classes)
All of these techniques accomplish the same thing - they confine knowledge of implementation details to the smallest possible part of a program. That is, they keep a secret of some sort.
Constant and liberal use of the above techniques is recommended.
An interesting quote from chapter one of Design Patterns, regarding the use of generic references :
"This so greatly reduces implementation dependencies between subsystems that it leads to the following principle of reusable object-oriented design :

Program to an interface, not an implementation.
Don't declare variables to be instances of particular concrete classes. Instead, commit only to an interface defined by an abstract class. You will find this to be a common theme of the design patterns in this book."
(They state a second principle as well: "Favor object composition over class inheritance.")

Abstract Factory
Using references to interfaces instead of references to concrete classes is an important way of minimizing ripple effects. The user of an interface reference is always protected from changes to the underlying implementation.
The Abstract Factory pattern is one example of this technique. Users of an Abstract Factory can create families of related objects without any knowledge of their concrete classes. (A typical business application would usually not need to use this technique - it is more suitable for toolkits or libraries.)

Example
An Abstract Factory is a major part of the full Data Access Object scheme. Here, the idea is to allow the business layer to interact with the data layer almost entirely through interface references. The business layer remains ignorant of the concrete classes which implement the datastore.

There are two distinct families of items here :
§  the various datastore implementations (MySql, FileScheme)
§  the various business objects which need persistence (User, Device, etc.)
This corresponds to the two operations which must be done to return a persisted object. The type of datastore is first determined (an implementation of DAOFactory is returned), using a Factory Method.
(This example would be much improved by not having imports of ServletConfig all over the place.)

package myapp.data;

import javax.servlet.ServletConfig;

/**
* Allows selection of a DAOFactory, without the user being
* aware of what choices are available.
*
* This style allows the data layer to make the decision regarding what
* DAOFactory is to be used by the business layer.
*/
public final class DatastoreSelector {

  /**
  * @param aConfig is non-null.
  */
  public static DAOFactory getDAOFactory( ServletConfig aConfig ){
    //demonstrate two implementation styles :
    return stringMappingImpl( aConfig );
    //return classNameImpl( aConfig );
  }

  // PRIVATE //

  /**
  * Use an ad hoc String mapping scheme, and introduce an if-else
  * branch for each alternative.
  */
  private static DAOFactory stringMappingImpl( ServletConfig aConfig ){
    if ( aConfig == null ) {
      throw new IllegalArgumentException("ServletConfig must not be null.");
    }
    //examine the config to extract the db identifier
    final String storageMechanism = aConfig.getInitParameter("DatastoreName");
    if ( storageMechanism.equals("MySql")) {
      return new DAOFactoryMySql( aConfig );
    }
    else if ( storageMechanism.equals("FileScheme") ) {
      return new DAOFactoryFileScheme( aConfig );
    }
    else {
      throw new IllegalArgumentException("Unknown datastore identifier.");
    }
  }

  /**
  * Make direct use of the class name, and use reflection to create the
  * object.
  */
  private static DAOFactory classNameImpl( ServletConfig aConfig ){
    DAOFactory result = null;
    //examine the config to extract the class name
    final String storageClassName = aConfig.getInitParameter("DatastoreClassName");
    try {
      Class storageClass = Class.forName(storageClassName);
      //Class.newInstance can be used only if there is a no-arg constructor ;
      //otherwise, use Class.getConstructor and Constructor.newInstance.
      Class[] types = { javax.servlet.ServletConfig.class };
      java.lang.reflect.Constructor constructor = storageClass.getConstructor(types);
      Object[] params = { aConfig };
      result = (DAOFactory) constructor.newInstance( params );
    }
    catch (Exception ex){
      System.err.println("Cannot create DAOFactory using name: " + storageClassName);
      ex.printStackTrace();
    }
    return result;
  }
}


package myapp.data;

/**
* Returns an implementation of all XXXDAO interfaces.
*/
public interface DAOFactory {

  /**
  * Returns an implementation of DeviceDAO, specific to a
  * particular datastore.
  */
  DeviceDAO getDeviceDAO() throws DataAccessException;

  /**
  * Returns an implementation of UserDAO, specific to a
  * particular datastore.
  */
  UserDAO getUserDAO() throws DataAccessException;
}


Then, each
DAOFactory implementation can return its implementations of the XXXDAO interfaces (DeviceDAO, UserDAO), which are the concrete worker classes which implement persistence.

package myapp.data;

import javax.servlet.ServletConfig;

/**
* Package-private implementation of DAOFactory.
* This is for a MySql database.
*/
final class DAOFactoryMySql implements DAOFactory {

  DAOFactoryMySql( ServletConfig aServletConfig ){
    if ( aServletConfig == null ) {
      throw new IllegalArgumentException("ServletConfig must not be null.");
    }
    fConfig = aServletConfig;
  }

  public UserDAO getUserDAO() throws DataAccessException {
    return new UserDAOMySql(fConfig);
  }

  public DeviceDAO getDeviceDAO() throws DataAccessException {
    return new DeviceDAOMySql(fConfig);
  }

  /// PRIVATE ////
  private final ServletConfig fConfig;
}


package myapp.data;

import javax.servlet.ServletConfig;

/**
* Package-private implementation of DAOFactory.
* This is for an ad hoc file scheme.
*/
final class DAOFactoryFileScheme implements DAOFactory {

  DAOFactoryFileScheme( ServletConfig aServletConfig ){
    if ( aServletConfig == null ) {
      throw new IllegalArgumentException("ServletConfig must not be null.");
    }
    fConfig = aServletConfig;
  }

  public UserDAO getUserDAO() throws DataAccessException {
    return new UserDAOFileScheme(fConfig);
  }

  public DeviceDAO getDeviceDAO() throws DataAccessException {
    return new DeviceDAOFileScheme(fConfig);
  }

  /// PRIVATE ////
  private final ServletConfig fConfig;
}


Here is an example of a
XXXDAO interface, and a toy implementation for a MySql database.

package myapp.data;

import myapp.business.Device;

/**
* The business layer talks to the data layer about storage of Device objects
* through a DeviceDAO reference.
*
* DataAccessException is a wrapper class, which exists only to wrap
* low-level exceptions specific to each storage mechanism (for example,
* SQLException and IOException). When an implementation class throws
* an exception, it is caught, wrapped in a DataAccessException, and then
* rethrown. This protects the business layer from ripple effects caused by
* changes to the datastore implementation.
*/
public interface DeviceDAO {
  Device fetch( String aId ) throws DataAccessException;
  void add( Device aDevice ) throws DataAccessException;
  void change( Device aDevice ) throws DataAccessException;
  void delete( Device aDevice ) throws DataAccessException;
}



package myapp.data;

import myapp.business.Device;
import java.net.InetAddress;
import javax.servlet.ServletConfig;

/**
* An implementation of DeviceDAO which is specific to a MySql database.
*
* This class must be package-private, to ensure that the business layer
* remains unaware of its existence.
*
* Any or all of these methods can be declared as synchronized. It all depends
* on the details of your implementation.
*
* Note that it is often possible to use properties files (or ResourceBundles) to
* keep SQL out of compiled code, which is often advantageous.
*/
final class DeviceDAOMySql implements DeviceDAO {

  DeviceDAOMySql( ServletConfig aConfig ) {
    //..elided
  }

  public Device fetch( String aId ) throws DataAccessException {
    //create a SELECT using aId, fetch a ResultSet, and parse it into a Device
    return null; //toy implementation
  }

  synchronized public void  add( Device aDevice ) throws DataAccessException{
    //parse aDevice into its elements, create an INSERT statement
  }

  synchronized public void change( Device aDevice )  throws DataAccessException{
    //parse aDevice into its elements, create an UPDATE statement
  }

  synchronized public void delete( Device aDevice )  throws DataAccessException {
    //extract the Id from aDevice, create a DELETE statement
  }
}

It is important to note most of the data layer's concrete classes are package-private - only
DatastoreSelector and DataAccessException are public.


No comments:

Post a Comment