DAO
Code that depends on specific features of data resources ties together business logic with data access logic. This makes it difficult to replace or modify an application's data resources.
The Data Access Object (or DAO) pattern:
Core J2EETM Patterns
The Java Pet Store sample application uses the DAO pattern both for database vendor-neutral data access, and to represent XML data sources as objects.
A Data Access Object class can provide access to a particular data resource without coupling the resource's API to the business logic. For example, sample application classes access catalog categories, products, and items using DAO interface CatalogDAO
.
Reimplementing CatalogDAO
for a different data access mechanism (to use a Connector, for example), would have little or no impact on any classes that use CatalogDAO
, because only the implementation would change. Each potential alternate implementation of CatalogDAO
would access data for the items in the catalog in its own way, while presenting the same API to the class that uses it.
The following code excerpts illustrate how the sample application uses the DAO pattern to separate business logic from data resource access mechanisms:
CatalogDAO
defines the DAO API. Notice that the methods in the interface below make no reference to a specific data access mechanism. For example, none of the methods specify an SQL query, and they throw only exceptions of type CatalogDAOSysException
. Avoiding mechanism-specific information in the DAO interface, including exceptions thrown, is essential for hiding implementation details.
public interface CatalogDAO {
public Category getCategory(String categoryID, Locale l)
throws CatalogDAOSysException;
public Page getCategories(int start, int count, Locale l)
throws CatalogDAOSysException;
public Product getProduct(String productID, Locale l)
throws CatalogDAOSysException;
public Page getProducts(String categoryID, int start, int count, Locale l)
throws CatalogDAOSysException;
public Item getItem(String itemID, Locale l)
throws CatalogDAOSysException;
public Page getItems(String productID, int start, int size, Locale l)
throws CatalogDAOSysException;
public Page searchItems(String query, int start, int size, Locale l)
throws CatalogDAOSysException;
}
CloudscapeCatalogDAO
implements this interface for the Cloudscape relational database, as shown in the following code excerpt. Note that the SQL to access the Cloudscape database is hard-coded.
public class CloudscapeCatalogDAO implements CatalogDAO {
...
public static String GET_CATEGORY_STATEMENT
= "select name, descn "
+ " from (category a join category_details b on a.catid=b.catid) "
+ " where locale = ? and a.catid = ?";
...
public Category getCategory(String categoryID, Locale l)
throws CatalogDAOSysException {
Connection c = null;
PreparedStatement ps = null;
ResultSet rs = null;
Category ret = null;
try {
c = getDataSource().getConnection();
ps = c.prepareStatement(GET_CATEGORY_STATEMENT,
ResultSet.TYPE_SCROLL_INSENSITIVE,
ResultSet.CONCUR_READ_ONLY);
ps.setString(1, l.toString());
ps.setString(2, categoryID);
rs = ps.executeQuery();
if (rs.first()) {
ret = new Category(categoryID, rs.getString(1), rs.getString(2));
}
rs.close();
ps.close();
c.close();
return ret;
} catch (SQLException se) {
throw new CatalogDAOSysException("SQLException: " + se.getMessage());
}
}
...
}
ScreenDefinitionsDAO
, described below and shown in Figure 1 above, is an example of a class that directly implements a DAO interface. This approach separates the data access interface from the details of how it is implemented, providing the benefits of the DAO pattern. The data access mechanism can be changed easily by writing a new class that implements the same interface, and changing client code to use the new class. Yet this approach is inflexible because it requires a code changes to modify the data access mechanism. For example, the sample application uses factory class CatalogDAOFactory
to select the class that implements the DAO interface for the catalog. Figure 2 below presents a structure diagram of the Data Access Object design pattern using a factory to select a DAO implementation.
Figure 2. A pluggable DAO |
At runtime, the CatalogHelper
uses the CatalogDAOFactory
to create an object that implements CatalogDAO
. The factory looks up the name of the class that implements the DAO interface in environment entry " param/CatalogDAOClass
". The CatalogHelper
accesses the catalog data source exclusively using the object created by the factory. In the example shown in the figure, the environment entry was set to the (fully-specified) name of class CloudscapeCatalogDAO
. This class implements the catalog DAO interface in terms of JDBCTM data sources, accessing a Cloudscape relational database.
This approach is more flexible than using a hard-coded class. To add a new type of data source, an application developer would simply create a class that implements CatalogDAO
in terms of the new data source type, specify the implementing class's name in the environment entry, and re-deploy. The factory would create an instance of the new DAO class, and the application would use the new data source type.
The sample application reduces redundant code by using a "generic DAO" that externalizes the SQL for different JDBC data sources. Figure 3 below shows how the sample application uses an XML file to specify the SQL for different JDBC data sources.
Figure 3. Externalizing DAO SQL |
In the figure, the CatalogDAOFactory
has selected an object of type GenericCatalogDAO
as the DAO to access the catalog. An XML file called CatalogDAOSQL.xml
specifies the SQL for each supported operation on each type of database. GenericCatalogDAO
configures itself by selecting a group of SQL statements from the XML file that correspond to the database type named by environment entry " param/CatalogDAODatabase
". The code sample below shows the definition of the XML for the getCategory
operation of the CatalogDAO
. Different SQL is specified for "cloudscape" and "oracle" database types.
select name, descn
from (category a join category_details b
on a.catid=b.catid)
where locale = ? and a.catid = ?
...
select name, descn
from category a, category_details b
where a.catid = b.catid and locale = ? and a.catid = ?
...
Method GenericCatalogDAO.getCategory
chooses the SQL corresponding to the configured database type, and uses it to fetch a category from the database via JDBC. The following code excerpt shows shows the implementation of the method.
public Category getCategory(String categoryID, Locale locale)
throws CatalogDAOSysException {
Connection connection = null;
ResultSet resultSet = null;
PreparedStatement statement = null;
try {
connection = getDataSource().getConnection();
String[] parameterValues = new String[] { locale.toString(), categoryID };
statement = buildSQLStatement(connection, sqlStatements,
XML_GET_CATEGORY, parameterValues);
resultSet = statement.executeQuery();
if (resultSet.first()) {
return new Category(categoryID, resultSet.getString(1), resultSet.getString(2));
}
return null;
} catch (SQLException exception) {
throw new CatalogDAOSysException("SQLException: " + exception.getMessage());
} finally {
closeAll(connection, statement, resultSet);
}
}
Notice that the method catches any possible SQLException
and converts it to a CatalogDAOSysException
, hiding the implementation detail that the DAO uses a JDBC database.
This strategy supports multiple JDBC databases with a single DAO class. It both decreases redundant code, and makes new database types easier to add. To support a new database type, a developer simply adds the SQL statements for that database type to the XML file, updates the environment entry to use the new type, and redeploys.
The pluggable DAO and generic DAO strategies can be used separately. If you know that a DAO class will only ever use JDBC databases (for example), the generic DAO class can be hardwired into the application, instead of selected by a factory. For maximum flexibility, the sample application uses both a factory method and a generic DAO.
A data access object can represent data that is not stored in a database. The sample application uses the DAO pattern to represent XML data sources as objects. Sample application screens are defined in an XML file which is interpreted by the class ScreenDefinitionDAO
. Specifying screen definitions externally makes access to the screen definitions more flexible. For example, if the application designers (or maintainers) decide to change the application to store screen descriptions in the database, instead of in an XML file, they would need only to implement a single new class ( ScreenFlowCloudscapeDAO
, for example). The code that uses ScreenDefinitionDAO
would remain unchanged, but the data would come from the database via the new class.
The screen definitions mechanism in the sample application provides an example of a concrete Data Access Object representing an underlying, non-database resource (an XML file).
Figure 1. Data Access Object providing access to XML data source |
Figure 1 shows a structure diagram of the ScreenDefinitionDAO
managing the loading and interpretation of XML data that defines application screens.
TemplateServlet
uses the ScreenDefinitionDAO
to load screen definitions:
Screens screenDefinitions =
ScreenDefinitionDAO.loadScreenDefinitions(screenDefinitionURL);
ScreenDefinitionDAO
represents screen definitions in an XML file deployed with an application. It uses XML APIs to read the screen definitions from the XML file. Only this class would need to be replaced to support storing screen definitions in some other way. The method that loads screen definitions looks like this:
public static Screens loadScreenDefinitions(URL location) {
Element root = loadDocument(location);
if (root != null) return getScreens(root);
else return null;
}
...
public static Screens getScreens(Element root) {
// get the template
String defaultTemplate = getTagValue(root, DEFAULT_TEMPLATE);
if (defaultTemplate == null) {
System.err.println("*** ScreenDefinitionDAO error: " +
" Default Template not Defined.");
return null;
}
Screens screens = new Screens(defaultTemplate);
getTemplates(root, screens);
// get screens
NodeList list = root.getElementsByTagName(SCREEN);
for (int loop = 0; loop < list.getLength(); loop++) {
Node node = list.item(loop);
if ((node != null) && node instanceof Element) {
String templateName = ((Element)node).getAttribute(TEMPLATE);
String screenName = ((Element)node).getAttribute(NAME);
HashMap parameters = getParameters(node);
Screen screen = new Screen(screenName, templateName, parameters);
if (!screens.containsScreen(screenName)) {
screens.addScreen(screenName, screen);
} else {
System.err.println("*** Non Fatal errror: Screen " + screenName +
" defined more than once in screen definitions file");
}
}
}
return screens;
}
...
The code fragment above shows how loadScreenDefinitions
loads screen definitions using DOM interfaces, while hiding that fact from clients of the class. A client of this class can expect to receive a Screens
object regardless of how those screens are loaded from persistent storage. Method getScreens
handles all of the DOM-specific details of loading a screen from an XML file.