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>

The JdbcDaoFactory class loads the data from the configuration file from within a private helper method, getDomainMap(), as follows:

private final DomainMap getDomainMap() throws DaoException
{
    if( m_DomainMap == null )
    {
        try
        {
            final Digester digester = new Digester();
            digester.setDebug( m_Debug );
            // DomainMap Rules...
            digester.addObjectCreate( "domain-map",
"com.carmanconsulting.dao.jdbc.DomainMap" );
            // ObjectMap Rules...
            digester.addObjectCreate( "domain-map/object-map",
"com.carmanconsulting.dao.jdbc.ObjectMap" );
            digester.addSetProperties( "domain-map/object-map" );
            digester.addSetNext( "domain-map/object-map", 
"addObjectMap" );
            // PropertyMap Rules...
            digester.addObjectCreate( "domain-map/object-map/property-
map","com.carmanconsulting.dao.jdbc.PropertyMap" );
            digester.addSetProperties( "domain-map/object-map/property-map"
);
            digester.addSetNext( "domain-map/object-map/property-map",
"addPropertyMap" );
            // SqlConverter Rules...
            digester.addObjectCreate( "domain-map/object-map/property-
map/sql-converter", null, "converterClass" );
            digester.addSetNext( "domain-map/object-map/property-map/sql-
converter", "setSqlConverter", "com.carmanconsulting.sql.convert.SqlConverter"
);
            
            m_DomainMap = ( DomainMap )digester.parse(
JdbcDaoFactory.class.getClassLoader().getResource( m_DomainMapFileName
).toString() );
        }
        catch( SAXException sax )
        {
            throw new DaoException( sax );
        }
        catch( IOException io )
        {
            throw new DaoException( io );
        }
    }
    return m_DomainMap;
}

The JdbcDaoFactory class doesn’t use the DomainMap object directly, but uses it to create JdbcDao objects:

public final Dao createDao() throws DaoException
{
    return new JdbcDao( getConnection(), getDomainMap() );
}

The JdbcDaoFactory.getConnection() method merely calls the DataSource.getConnection() method on the javax.sql.DataSource instance found in the default JNDI initial context.

Implement persistence methods

The Persistence API for the JDBC DAO provider involves three classes:

  • PersistenceStatement: Uses a java.sql.PreparedStatement instance, which is created internally based on the Data Manipulation Language (DML) string and the java.sql.Connection provided to PersistenceStatement‘s constructor to perform persistence updates to the database. PersistenceStatement objects are connection specific, operation specific (create, update, or delete), and class specific. That is, a specific PersistenceStatement instance performs a specific persistence operation, say delete, for a specific class, using a java.sql.PreparedStatement object created from the specified java.sql.Connection object. The parameterMaps parameter supplied to the constructor contains PropertyMap objects corresponding to the parameters defined in the dml parameter (in the same order). These PropertyMap objects help bind the parameters to the java.sql.PreparedStatement:

    class PersistenceStatement
    {
        PersistenceStatement( Connection connection, PropertyMap[] 
    parameterMaps, String dml );
        void execute( Object o ) throws DaoException;
        void close() throws DaoException;
    }
    
  • PersistenceStatementFactory: Constructs (and caches) DML strings and arrays containing the PropertyMap objects. Those objects correspond to the DML string parameters for each persistence operation associated with the object type represented by the enclosed ObjectMap instance (say that three times fast). This class uses these DML strings, PropertyMap arrays, and the provided java.sql.Connection object to create PersistenceStatement objects for each persistence operation. PersistenceStatementFactory objects are class-specific only; that is, they only correspond to a particular class. They maintain no references to any java.sql.Connection objects. Therefore, they are cached by the DomainMap instance that JdbcDaoFactory uses:

    class PersistenceStatementFactory
    {
        PersistenceStatementFactory( ObjectMap objectMap );
        PersistenceStatement createCreateStatement( Connection connection )
    throws DaoException;
        PersistenceStatement createUpdateStatement( Connection connection )
    throws DaoException;
        PersistenceStatement createDeleteStatement( Connection connection )
    throws DaoException;
    }
    
  • PersistenceDelegate: Maintains a reference to three PersistenceStatement instances, corresponding to the three persistence operations for a specific class. The PersistenceStatement objects are created by the specified PersistenceStatementFactory, using the supplied java.sql.Connection object. This class delegates persistence operations to the appropriate, enclosed PersistenceStatement object. PersistenceDelegate objects are both class specific and connection specific. That is, they perform persistence operations for a specific class, using a specific database connection:

    class PersistenceDelegate
    {
        PersistenceDelegate( Connection connection, PersistenceStatementFactory
    statementFactory );
        void create( Object o ) throws DaoException;
        void update( Object o ) throws DaoException;
        void delete( Object o ) throws DaoException;
        void close() throws DaoException;
    }
    

When it comes to the persistence methods, the JdbcDao class merely delegates the request to the appropriate PersistenceDelegate object as follows:

private final Map m_PersistenceDelegateMap = new HashMap();
public void delete(Object o) throws DaoException
{
    getPersistenceDelegate( o ).delete( o );
}
private final PersistenceDelegate getPersistenceDelegate( final Object o )
throws DaoException
{
    PersistenceDelegate persistenceDelegate =
(PersistenceDelegate)m_PersistenceDelegateMap.get(o.getClass().getName());
    if(persistenceDelegate == null)
    {
        persistenceDelegate = new PersistenceDelegate(m_Connection,
m_DomainMap.getPersistenceStatementFactory( o.getClass() ));
        m_PersistenceDelegateMap.put(o.getClass().getName(),
persistenceDelegate);
    }
    return persistenceDelegate;
}

The only code that corresponds to the delete operation (delete) implemented here occurs within the PersistenceStatementFactory class. The following code creates a PersistenceStatement for the delete operation:

private final ObjectMap m_ObjectMap;
private PropertyMap[] m_DeleteParameters;
private String m_DeleteDml;
PersistenceStatement createDeleteStatement( Connection connection ) throws
DaoException
{
    return new PersistenceStatement(connection, getDeleteParameters(),
getDeleteDml());
}
private final PropertyMap[] getDeleteParameters()
{
    if( m_DeleteParameters == null )
    {
        final List allParameters = new LinkedList();
        CollectionUtils.addAll( allParameters, m_ObjectMap.getKeyPropertyMaps()
);
        m_DeleteParameters = createParametersArray( allParameters );
    }
    return m_DeleteParameters;
}
    
private final String getDeleteDml()
{
    if( m_DeleteDml == null )
    {
        final StringBuffer dmlBuffer = new StringBuffer( "DELETE FROM " );
        dmlBuffer.append( m_ObjectMap.getTableName() );
        dmlBuffer.append( " WHERE " );
        for( Iterator i = m_ObjectMap.getKeyPropertyMaps(); i.hasNext(); )
        {
            final PropertyMap propertyMap = ( PropertyMap )i.next();
            dmlBuffer.append( propertyMap.getColumnName() );
            dmlBuffer.append( "=?" );
            if( i.hasNext() )
            {
                dmlBuffer.append( " AND " );
            }
        }
        m_DeleteDml = dmlBuffer.toString();
    }
    return m_DeleteDml;
}

Implementing the other two persistence operations proves similar and fairly straightforward. The real trick to writing your own provider lies in the query-processing engine.

The DAOQL query-processing engine

By far, the most difficult part of developing your own JDBC DAO provider is the DAOQL processing piece. However, with the help of the SableCC framework (see The Sable Research Group), we can overcome the difficult task of creating our own query string parser. The SableCC framework generates object-oriented, compiler code based on a grammar file. The generated code parses the query strings into a strictly typed abstract syntax tree (AST). By providing an apply( Switch sw ) method, the AST lets us use the Visitor design pattern to achieve double dispatch in Java (see “Sidebar 2: The Visitor Design Pattern and Double Dispatch“). We will implement the Switch interface (indirectly, by extending an adapter class), which contains a callback method for each type the framework creates to represent the grammar. First, let’s look at the production rules for the simplified grammar used in this example:

query = select distinct? [object_name]:identifier in
[domain_alias]:identifier where [where_clause]:disjunction;
disjunction = {single} conjunction |
              {multiple} disjunction or conjunction;
conjunction = {single} relational |
              {parenthetical} left_paren disjunction right_paren |
              {multiple} conjunction and relational;
relational = {binary} [left]:value relational_operator [right]:value |
             {between} [value]:value between [lower_bound]:value and
[upper_bound]:value;
relational_operator = {gt} greater_than | 
                      {lt} less_than | 
                      {gte} greater_than_or_equal | 
                      {lte} less_than_or_equal |
                      {eq} equal |
                      {neq} not_equal;       
value = {field} [object_name]:identifier dot [field_name]:identifier |
        {literal} literal;
literal = {double} double | {long} long | {string} string;

This grammar supports query strings of the following formats:

select p in Person where p.firstName="James" and p.lastName="Carman"
select p in Person where p.lastName between 'Carman' and 'Cartman'
select p in Person where p.lastName >= 'Carman'

Don’t worry if you don’t understand the grammar; I’ve left out quite a bit, like definitions of such things as the not_equal token. The grammar is limited, but it should provide an adequate example and support some simple queries. Now, based on this grammar, SableCC generates classes corresponding to each production rule. So, for the between relational production rule (the relational alternative with {between} in front of it), we have the ABetweenRelational class, which looks like:

public final class ABetweenRelational extends PRelational
{
    public PValue getValue();
    public TBetween getBetween();
    public PValue getLowerBound();
    public TAnd getAnd();
    public PValue getUpperBound();
    public void apply( Switch sw );
}

The types starting with T represent tokens in the grammar. The Switch interface defines methods, such as caseABetweenRelational(ABetweenRelational node) that correspond to each type the framework generates. The framework supplies a DepthFirstAdapter class that implements the Switch interface using a “depth-first” (when a node’s children are traversed before it is) tree traversal algorithm. In our case, we just need to override the case methods for the concerned types, such as the ABetweenRelational class, as follows:

class QueryProcessor extends DepthFirstAdapter
{
    private final StringBuffer m_SqlBuffer = new StringBuffer();
    String getSqlString()
    {
        return m_SqlBuffer.toString();
    }
    public void caseABetweenRelational(ABetweenRelational node)
    {
        node.getValue().apply( this );
        m_SqlBuffer.append( " BETWEEN " );
        node.getLowerBound().apply( this );
        m_SqlBuffer.append( " AND " );
        node.getUpperBound().apply( this );
    }
}

The QueryProcessor class translates the DAOQL query string into a SQL query string, storing it in the enclosed java.lang.StringBuffer object. Completely translating the DAOQL query string into a SQL string only involves overriding a few more methods of the DepthFirstAdapter class. Using this framework, parsing a DAOQL query string proves fairly straightforward:

QueryProcessor processor = new QueryProcessor();
Lexer lexer = new Lexer( new PushbackReader( new StringReader( queryString )
));
Parser parser = new Parser( lexer );
parser.parse().apply( processor );
String sql  = processor.getSqlString();

The SableCC framework also generates the Lexer and Parser classes. In fact, the code generated by the framework is entirely self-contained. There are no jar files to include on the classpath. Using the generated SQL string to query the database and map the results is somewhat trivial, so I won’t include it here.

Future enhancements

While designing and implementing the DAO framework, I discovered some of its limitations. The following sections outline some of those limitations and describe how I may address them in future versions.

Named DaoFactory instances

Many business applications deal with data from multiple data sources. Since the DaoFactory currently implements the Singleton design pattern, business objects can access only one data source. A solution to this problem would provide a variation to the Singleton design pattern by adding the following method to the DaoFactory class:

public static final DaoFactory getInstance( String dataSource ) throws
DaoException;

Adding this method allows one DaoFactory instance per data source.

Explicit transaction management

Although the framework supports transactions via the javax.transaction.UserTransaction interface or container-managed transactions, these services are available only within a J2EE container’s context. Situations may arise that mandate transactional support outside a J2EE container’s context (such as unit testing, two-tier applications, and so on). Adding the following methods to the Dao interface would allow explicit transaction management without J2EE container overhead:

public void beginTransaction() throws DaoException;
public void commitTransaction() throws DaoException;
public void rollbackTransaction() throws DaoException;

Prepared DAOQL queries

As the framework stands, it doesn’t lend itself to reusing commonly used queries, similar to the java.sql.PreparedStatement approach. Adding such functionality to the framework wouldn’t be a challenge, but it also wouldn’t allow the DAO provider to utilize its data store’s performance benefits (such as using java.sql.PreparedStatements). However, by adding a DaoQuery interface and modifying the Dao interface as follows, you might overcome this limitation:

public interface Dao
{
    public void create( Object o ) throws DaoException;
    public DaoQuery retrieve( String queryString ) throws DaoException;
    public void update( Object o ) throws DaoException;
    public void delete( Object o ) throws DaoException;
    public void close() throws DaoException;
}
public interface DaoQuery
{
    public Collection execute() throws DaoException;
    public void setParameter( int parameterIndex, Object parameterValue ) 
throws DaoException;
    public void close() throws DaoException;
}

The DAO of Java

The generic DAO framework outlined here provides a simplistic, pattern-driven, and powerful approach to object persistence. Business objects no longer must worry about object type-specific interfaces. They can use a generic API and let the DAO provider worry about which types are persisted or queried.

James Carman, an
independent consultant, has been working with Java since 1997. He
is a Sun Certified Developer for the Java 2 Platform, Sun Certified
Web Component Developer for J2EE, and a Sun Certified Architect for
Java Technology. He serves as codirector of the Cincinnati Java
Users Group, where he frequently gives talks about various Java
topics (J2EE architecture, JavaSpaces, JMS (Java Message Service),
and Java security, to name a few).

Source: www.infoworld.com