Write once, persist anywhere

Implement a Data Access Object pattern framework

The Data Access Object (DAO) pattern provides an abstraction layer between the business logic tier (business object) and the persistent storage tier (data source). Business objects access data sources via data access objects. This abstraction layer encapsulates the persistent storage type/vendor implementation. Ideally, changes made to the data source, such as switching database vendors or type, should not modify the business objects; only the data access objects themselves would need to change.

In this article, I explore a simple yet powerful framework for implementing the DAO design pattern. First, I describe a typical DAO pattern implementation strategy, noting its shortcomings. Then, I move on to the new implementation, explaining its classes. I also explore an example implementation using the JDBC (Java Database Connectivity) API as the storage mechanism. Finally, I present a wish list for future enhancements.

Typical DAO implementation

Sun Microsystems suggests a DAO pattern implementation in the J2EE Pattern Catalog that uses the Abstract Factory and Factory Method design patterns (see Design Patterns) and usually involves an interface similar to:

public interface DaoFactory
{
     public CustomerDao createCustomerDao();
     public AccountDao createAccountDao();
}

Here, the DaoFactory interface acts as the abstract factory, and the createCustomerDao() method acts as the factory method. In this example, data source-specific classes implement CustomerDao and AccountDao interfaces (or abstract classes); those interfaces typically resemble the following:

public interface CustomerDao
{
     public List getCustomersByLastName( String lastName );
     public Customer getCustomerBySSN( String ssn );
}

Although this implementation provides adequate data source-type independence, it introduces object type dependence. For example, adding another object type, say Vendor, to the business domain involves introducing a new VendorDao interface and adding a new getVendorDao() method to the DaoFactory interface.

A new and improved DAO implementation

To provide object type independence, you must simplify the DaoFactory class and add another layer to the DAO implementation hierarchy (the Dao interface) as follows:

public abstract class DaoFactory
{
     public final DaoFactory getInstance() throws DaoException;
     public abstract Dao createDao() throws DaoException;
}
public interface Dao
{
     public void create( final Object object ) throws DaoException;
     public Collection retrieve( final String queryString) throws 
DaoException;
     public void update( final Object object ) throws DaoException;
     public void delete( final Object object ) throws DaoException;
     public void close() throws DaoException;
}

The new, simplified DaoFactory class (now implemented as an abstract class instead of an interface) can remain unchanged as you add new object types to the business domain, as the class merely references the generic Dao interface, not object type-specific DAO interfaces. The DaoFactory class still implements the Abstract Factory and Factory Method design patterns; however, it now also implements the Singleton design pattern, via the static getInstance() method. The getInstance() method returns a DaoFactory type specified at runtime by an XML configuration file with the following format:

<dao-factory factoryClass="MyDaoFactory">
    <property name="property1" value="value1" />
    <property name="property2" value="value2" />
</dao-factory>

The getInstance() method uses the org.apache.commons.digester.Digester class (see "Sidebar 1: Apache Software Foundation's Digester Class") to parse the XML configuration file as follows:

private static final String CONFIG_FILE_NAME = "DaoFactory.xml";
private static DaoFactory m_Instance;
public synchronized static final DaoFactory getInstance() throws DaoException
{
    final Category category = Category.getInstance( 
DaoFactory.class );
    if( m_Instance == null )
    {
        try
        {
            final Digester digester = new Digester();
            digester.addObjectCreate( "dao-factory", null, 
"factoryClass" );
            digester.addSetProperty( "dao-factory/property", 
"name", "value");
            m_Instance = ( DaoFactory )digester.parse(
DaoFactory.class.getClassLoader().getResource( CONFIG_FILE_NAME ).toString()
);
        }
        catch( SAXException sax )
        {
            throw new DaoException( "Unable to parse configuration
document.", sax );
        }
        catch( IOException io )
        {
            throw new DaoException( "Unable to parse configuration
document.", io );
        }
    }
    return m_Instance;
}

Business objects should always use the getInstance() method to obtain a reference to a DaoFactory object, as the method lets you substitute different DaoFactory implementations as needed without changing the business object code. This dynamic instantiation and initialization mechanism requires that DaoFactory subclasses provide a no-argument, default constructor and should conform to the property setter method naming conventions described in the JavaBeans 1.0.1 specification.

The Dao interface

As you can see, the new Dao interface implements the CRUD (create, retrieve, update, and delete) design pattern. The persistence modifying methods, create(), update(), and delete(), now accept generic Object parameters, as opposed to Customer or Account parameters.

A Dao object represents one connection to the data source, similar to a java.sql.Connection, and should be treated as such. Business objects should adhere to the following guidelines when interacting with Dao objects:

  1. Do not cache references to Dao objects. Each time a business object requires a Dao object, it should allow the DaoFactory to create one for it.
  2. Dao objects are not thread-safe. Do not use them in multiple threads.
  3. Be sure to call the close() method on the Dao object, as it may release valuable system resources; for example, by closing database connections or JNDI (Java Naming and Directory Interface) contexts.
  4. Objects managed by a Dao object should follow the JavaBeans specification with respect to providing an accessible default constructor and naming property setter and getter methods.

The DAO query language

To maintain data source independence via this framework, however, we must establish a new data source-independent query syntax for the Dao interface's retrieve() method to use. The Enterprise JavaBeans (EJB) 2.0 specification addresses exactly the same issue via the EJB query language (EJBQL), providing a simple syntax definition closely resembling SQL (as most EJB vendors use relational databases for container-managed persistence). However, EJBQL, as specified in EJB 2.0, remains somewhat limited and does not provide an adequate model for the DAO query language (DAOQL). Although DAOQL's definition is beyond the scope of this article, it should meet these requirements:

  1. It should not target any specific data store type. Since this framework is designed to be data store-independent, the language should avoid introducing features existent in one data store type and not in others.
  2. It should be as robust as possible, providing a vast feature set so that business objects don't need to circumvent the framework to retrieve information from the data store.
  3. It should not introduce many novel or unfamiliar concepts, so as to allow business developers to use the query language with little or no assistance.

Given these requirements, the Object query language (OQL) is an ideal candidate from which to model the DAOQL, as the OQL is a comprehensive, object-based query language, already familiar to many business developers.

The DAO provider architecture

The DaoFactory class's plug-and-play nature and the added abstraction layer between the business objects and the data store provide us with a service provider interface, or SPI (to borrow a term from the JNDI provider architecture), for third-party (or in-house) DAO providers to use. The beauty of this new SPI: it matches exactly the API business objects use. A DAO provider must subclass only the DaoFactory class and implement the Dao interface as necessary for its data source type. Thus, a DAO provider must implement only five methods from the Dao interface and one method from the DaoFactory abstract class. This obligates our DAO provider to a whopping six methods! Admittedly, this is easier said than done, but as you will see, implementing your own DAO provider doesn't have to be such a daunting task, provided you use the right tools.

Implement a JDBC DAO provider

In this section, I show how easily you can implement your own JDBC DAO provider, using a few open source utilities. All the classes you use to implement the JDBC DAO provider are contained in the com.carmanconsulting.dao.jdbc package. First, I devise an object-to-relational mapping model for use throughout the framework. Then, I implement one persistence method, delete(). Finally, I construct a portion of the DAOQL query-processing engine. The primary classes for this provider are JdbcDaoFactory and JdbcDao:

public class JdbcDaoFactory extends DaoFactory
{
     public Dao createDao();
     void setDomainMapFileName( String domainMapFileName );
     void setDebug( int debug );
     void setDataSourceName( String dataSource );
}
class JdbcDao implements Dao
{
     public void create( Object o );
     public Collection retrieve( String queryString );
     public void update( Object o );
     public void delete( Object o );
     public void close();
}

The JdbcDaoFactory class uses a javax.sql.DataSource object to establish connections to the database. The dataSourceName property represents a JNDI name used to look up the javax.sql.DataSource instance within the default JNDI initial context. The domainMapFileName property locates the configuration file for the object-to-relational mapping model.

The object-to-relational mapping model

The object-to-relational mapping model involves only a few classes:

public class DomainMap
{
     PersistenceStatementFactory getPersistenceStatementFactory( Class
objectClass );
     ObjectMap getObjectMap( String domainAlias );
     void addObjectMap( ObjectMap objectMap );
}
public class ObjectMap
{
     addPropertyMap( PropertyMap propertyMap );
     String getDomainAlias();
     void setDomainAlias( String domainAlias );
     String getObjectClassName();
     void setObjectClassName( String objectClassName );
     String getTableName();
     void setTableName( String tableName );
     PropertyMap getPropertyMap( String propertyName );
     Iterator getKeyPropertyMaps();
     Iterator getNonKeyPropertyMaps();
}
public class PropertyMap
{
     SqlConverter getSqlConverter();
     void setSqlConverter( SqlConverter sqlConverter );
     String getColumnName();
     void setColumnName( String columnName );
     String getColumnType();
     void setColumnType( String columnType );
     String getPropertyName();
     void setPropertyName( String propertyName );
     boolean isKey();
     void setKey( Boolean key );
}
public interface SqlConverter
{
     public static final SqlConverter NULL_CONVERTER;
     Object toSqlType( Object o );
     Object fromSqlType( Object o );
}

These classes, although simple, merit some explanation. First, the DomainMap class simply provides a container for using the ObjectMap instances throughout the framework. It provides a convenience method for obtaining a reference to an ObjectMap instance corresponding to a domain alias. Domain aliases act as shorthand for class names in DAOQL query strings. I cover the PersistenceStatementFactory class in the "Implement Persistence Methods" section.

The ObjectMap class represents the mapping settings for a particular class within the domain. It contains convenience methods for obtaining references to its contained PropertyMap instances. Each class within the domain is assigned a unique domain alias. Also, in this object-to-relational mapping framework, each class maps to exactly one table.

Finally, the PropertyMap class embodies the mapping settings for each persistent property of a class in the domain. Each property maps one column within the assigned table. Some properties represent primary key values in the database tables (or at least within the domain). In instances where the database type and the property type are incompatible, a SqlConverter object performs the translation. As with much of the framework, we use an XML configuration file and the org.apache.commons.digester.Digester class to create a DomainMap class instance, corresponding to the data contained in the file. The configuration document adheres to the following DTD (document type definition):

<!ELEMENT domain-map (object-map*) >
<!ELEMENT object-map (property-map*) >
<!ATTLIST object-map
  objectClassName CDATA #REQUIRED
  domainAlias CDATA #REQUIRED
  tableName   CDATA #REQUIRED >
<!ELEMENT property-map (sql-converter?)>
<!ATTLIST property-map
  key          (true|false) "false"
  propertyName CDATA #REQUIRED
  columnName   CDATA #REQUIRED
  columnType
(ARRAY|BIGINT|BINARY|BIT|BLOB|CHAR|CLOB|DATE|DECIMAL|DISTINCT|DOUBLE|
FLOAT|INTEGER|JAVA_OBJECT|LONGVARBINARY|LONGVARCHAR|NULL|NUMERIC|OTHER|
REAL|REF|SMALLINT|STRUCT|TIME|TIMESTAMP|TINYINT|VARBINARY|VARCHAR ) 
#REQUIRED >
<!ELEMENT sql-converter EMPTY>
<!ATTLIST sql-converter 
  converterClass CDATA #REQUIRED>
1 2 3 Page 1