Newsletter sign-up
View all newsletters

Enterprise Java Newsletter
Stay up to date on the latest tutorials and Java community news posted on JavaWorld

Sponsored Links

Optimize with a SATA RAID Storage Solution
Range of capacities as low as $1250 per TB. Ideal if you currently rely on servers/disks/JBODs

Write once, persist anywhere

Implement a Data Access Object pattern framework

  • Print
  • Feedback

Page 4 of 5

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:

  • Print
  • Feedback

Resources