A Primer on Spring's Data Access Object (DAO) Framework
by Dhrubojyoti Kayal
11/29/2006
Enter Spring DAO
The problems listed above can be solved by identifying the portions of the code that vary, and then separating or encapsulating them from the code that remains fixed. The designers of Spring have done exactly that and have come out with an ultra-thin, robust, and highly extensible JDBC framework. The fixed parts (like retrieving connection, preparing the statement object, executing the query, and releasing the database resources) have been written once and correctly—so part of this framework helps to eliminate the shortcomings found in traditional JDBC-based DAO.
Figure 2 shows the major building blocks of the Spring JDBC framework. The business service objects continue to use the DAO implementation classes through appropriate interfaces.
JdbcDaoSupport
is the super class for JDBC data access objects. It is associated with a particular datasource. The Spring
Inversion of Control (IOC) container, or the
BeanFactory, is responsible for getting the appropriate datasource configuration details and associating them with
JdbcDaoSupport
. The most important functionality of this class is to make the
JdbcTemplate
object available to the subclasses.
Figure 2. Major components of the Spring JDBC framework
JdbcTemplate
is the most important class in the Spring JDBC framework. To quote the documentation, "it simplifies the use of JDBC and helps to avoid common errors. It executes core JDBC workflow, leaving application code to provide SQL and extract results." This class helps separate the static parts of JDBC DAO code by performing these boilerplate tasks:
- Retrieves connections from the datasource.
- Prepares appropriate statement object.
- Executes SQL CRUD operations.
- Iterates over result sets and populates the results in standard collection objects.
- Handles
SQLException
exceptions and translates them to a more error-specific exception hierarchy.
Rewriting Using Spring DAO
Now that you have a basic understanding of the Spring JDBC framework, it is time to rewrite the existing code. I will take a step-by-step approach and, in the process, discuss how to overcome the problems stated in the previous section.
Step 1: Alter DAO Implementation class - The
EmployeeDAOImpl
now extends from
JdbcDaoSupport
to get hold of
JdbcTemplate
.
import org.springframework.jdbc.core.support.JdbcDaoSupport;
import org.springframework.jdbc.core.JdbcTemplate;
public class EmployeeDAOImpl extends JdbcDaoSupport
implements IEmployeeDAO{
public List findBySalaryRange(Map salaryMap){
Double dblParams [] = {Double.valueOf((String)
salaryMap.get("MIN_SALARY"))
,Double.valueOf((String)
salaryMap.get("MAX_SALARY"))
};
//The getJdbcTemplate method of JdbcDaoSupport returns an
//instance of JdbcTemplate initialized with a datasource by the
//Spring Bean Factory
JdbcTemplate daoTmplt = this.getJdbcTemplate();
return daoTmplt.queryForList(FIND_BY_SAL_RNG,dblParams);
}
}
In the listing above, the values from the incoming parameter map are stored in a double array, in the same order as the positional parameters in the SQL string. The query results are returned by the
queryForList()
method as a
List
(one entry for each row) of Maps (one entry for each column, using the column name as the key). Later I'll show you how to return a list of transfer objects.
From the simplified code, it is evident that
JdbcTemplate
fosters reuse, and this has resulted in significant code reduction in the DAO implementation. The tight coupling with the JDBC and collection packages has been removed. Leakage of JDBC resource is no longer a problem as
JdbcTemplate
methods ensure that database resources are released in the proper sequence after they have been used.
In addition, you are not forced to handle exceptions while using Spring DAO. The
JdbcTemplate
class handles the
SQLException
, and translates it into a Spring-specific exception hierarchy based on SQL error codes or the error states. For example,
DataIntegrityViolationException
is raised when there is an attempt to insert duplicate values in a primary key column. However, you need not handle this exception if you cannot recover from this error. This is possible because the root exception class in Spring DAO,
DataAccessException
is a runtime exception. It is noteworthy that the Spring DAO exceptions are independent of the data access implementation. The same exceptions are raised if the implementation is provided by O/R mapping solutions.
Step 2: Alter Business Service - The business service now implements a new method setDao(), which is used by the Spring container to pass a reference of the DAO implementation class. This process is called "setter injection," and the Spring container is informed of this via the configuration file shown in Step 3. Note that it is no longer necessary to use the DAOFactory as the Spring BeanFactory compliments that functionality:
public class EmployeeBusinessServiceImpl
implements IEmployeeBusinessService {
IEmployeeDAO empDAO;
public List getEmployeesWithinSalaryRange(Map salaryMap){
List empList = empDAO.findBySalaryRange(salaryMap);
return empList;
}
public void setDao(IEmployeeDAO empDAO){
this.empDAO = empDAO;
}
}
You see the flexibility of P2I; there is only a minimal change in the business service implementation, even though I have overhauled the DAO implementation. The minimal impact is due to the business services now being managed by the Spring container.
Step 3: Configure Bean Factory - The Spring bean factory needs a configuration file to initialize and start the Spring framework. This configuration file enlists all the business service and DAO implementation classes with the Spring bean container. Apart from this, it also has the information to initialize the datasource and JdbcDaoSupport:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<!-- Configure Datasource -->
<bean id="FIREBIRD_DATASOURCE"
class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiEnvironment">
<props>
<prop key="java.naming.factory.initial">
weblogic.jndi.WLInitialContextFactory
</prop>
<prop key="java.naming.provider.url">
t3://localhost:7001
</prop>
</props>
</property>
<property name="jndiName">
<value>
jdbc/DBPool
</value>
</property>
</bean>
<!-- Configure DAO -->
<bean id="EMP_DAO" class="com.bea.dev2dev.dao.EmployeeDAOImpl">
<property name="dataSource">
<ref bean="FIREBIRD_DATASOURCE"></ref>
</property>
</bean>
<!-- Configure Business Service -->
<bean id="EMP_BUSINESS"
class="com.bea.dev2dev.sampleapp.business.EmployeeBusinessServiceImpl">
<property name="dao">
<ref bean="EMP_DAO"></ref>
</property>
</bean>
</beans>
The Spring bean container sets the datasource object with the DAO implementation, by invoking the
setDataSource()
method available from
JdbcDaoSupport
. It also takes the responsibility of supplying the business service with the DAO implementation.
Step 4: Test - Finally it's time to write the JUnit test class. Going by Spring philosophy, I will test it outside the container. However, as evident from the configuration file in Step 3, I have been using the WebLogic Server connection pool.
package com.bea.dev2dev.business;
import java.util.*;
import junit.framework.*;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
public class EmployeeBusinessServiceImplTest extends TestCase {
private IEmployeeBusinessService empBusiness;
private Map salaryMap;
List expResult;
protected void setUp() throws Exception {
initSpringFramework();
initSalaryMap();
initExpectedResult();
}
private void initExpectedResult() {
expResult = new ArrayList();
Map tempMap = new HashMap();
tempMap.put("EMP_NO",new Integer(1));
tempMap.put("EMP_NAME","John");
tempMap.put("SALARY",new Double(46.11));
expResult.add(tempMap);
}
private void initSalaryMap() {
salaryMap = new HashMap();
salaryMap.put("MIN_SALARY","1");
salaryMap.put("MAX_SALARY","50");
}
private void initSpringFramework() {
ApplicationContext ac = new FileSystemXmlApplicationContext
("C:/SpringConfig/Spring-Config.xml");
empBusiness =
(IEmployeeBusinessService)ac.getBean("EMP_BUSINESS");
}
protected void tearDown() throws Exception {
}
/**
* Test of getEmployeesWithinSalaryRange method,
* of class
* com.bea.dev2dev.business.EmployeeBusinessServiceImpl.
*/
public void testGetEmployeesWithinSalaryRange() {
List result = empBusiness.getEmployeesWithinSalaryRange
(salaryMap);
assertEquals(expResult, result);
}
}