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:
- Do not cache references to
Dao
objects. Each time a business object requires aDao
object, it should allow theDaoFactory
to create one for it. Dao
objects are not thread-safe. Do not use them in multiple threads.- Be sure to call the
close()
method on theDao
object, as it may release valuable system resources; for example, by closing database connections or JNDI (Java Naming and Directory Interface) contexts. - 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:
- 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.
- 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.
- 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 ajava.sql.PreparedStatement
instance, which is created internally based on the Data Manipulation Language (DML) string and thejava.sql.Connection
provided toPersistenceStatement
‘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 specificPersistenceStatement
instance performs a specific persistence operation, say delete, for a specific class, using ajava.sql.PreparedStatement
object created from the specifiedjava.sql.Connection
object. TheparameterMaps
parameter supplied to the constructor containsPropertyMap
objects corresponding to the parameters defined in thedml
parameter (in the same order). ThesePropertyMap
objects help bind the parameters to thejava.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 thePropertyMap
objects. Those objects correspond to the DML string parameters for each persistence operation associated with the object type represented by the enclosedObjectMap
instance (say that three times fast). This class uses these DML strings,PropertyMap
arrays, and the providedjava.sql.Connection
object to createPersistenceStatement
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 anyjava.sql.Connection
objects. Therefore, they are cached by theDomainMap
instance thatJdbcDaoFactory
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 threePersistenceStatement
instances, corresponding to the three persistence operations for a specific class. ThePersistenceStatement
objects are created by the specifiedPersistenceStatementFactory
, using the suppliedjava.sql.Connection
object. This class delegates persistence operations to the appropriate, enclosedPersistenceStatement
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.