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.

Spring DAO Architecture

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);        

    }     

}