Spring 数据访问对象(Data Access Object,DAO)框架入门

来源:互联网 发布:淘宝助理添加水印 编辑:程序博客网 时间:2024/05/18 05:09
摘要

J2EE 应用中的业务组件普遍采用 JDBC API 访问和修改关系型数据库的持久化数据。这样做常常会把持久化代码和业务逻辑混合在一起,实在太糟糕了。数据访问对象(Data Access Object,DAO)设计模式针对这一问题把持久化逻辑分离到数据访问类中,以达到分离目的。

本文是基于 DAO 设计模式的入门文章,把它的优点和缺点都毫无偏颇的展现出来。接着将引入 Spring 2.0 JDBC/DAO 框架,并将示范它是如何优雅的解决传统 DAO 设计的缺点。

传统 DAO 设计

Data Access Object (DAO)属于《Core J2EE Design Pattern》一书中所介绍的集成层设计模式范畴。它封装了持久化存储访问并以独立的层次来处理代码。本文中所提到的持久化存储是指关系型数据库。

DAO 模式在业务逻辑层和持久化存储层之间引入了新的抽象层,如图 1 所示。业务对象通过数据访问对象访问关系型数据库(数据源)。抽象层简化了应用程序代码增强了灵活性。太完美了,试想一下哪天改变了数据源,比如更换为其他数据库厂商的产品,只需要修改数据访问对象,并且对业务对象的影响也是最小的。

          图1. 使用 DAO 前后的应用程序结构

现在我解释一下 DAO 设计模式的基本含义,并写一些代码说明。下面的例子来自于某公司的域模型(domain model)。简单的来说,该公司有多名工作在不同部门的员工,比如销售、市场、人力资源。为了更简略,我将只针对“Employee”实体来说明。

面向接口编程

之所以 DAO 设计模式有如此的灵活性主要归功于对象设计的最佳实践:面向接口编程(Program to an Interface,P2I)。该原则是这样陈述的:具体的对象必须实现一个接口。换句话说,利用调用者(caller)编程胜于利用具体对象本身。因此,你可以容易的替代不同的实现,仅仅需要改动很少的客户端代码(译注:这里的客户端代码应该是指业务代码)。

下面要开始定义 Employee DAO 接口了,具有 findBySalaryRange() 行为的 IEmployeeDAO。业务组件将通过这些接口与 DAO 交互:
import java.util.Map;
public interface IEmployeeDAO {
  
//SQL String that will be executed
  public String FIND_BY_SAL_RNG = "SELECT EMP_NO, EMP_NAME, "
  
+ "SALARY FROM EMP WHERE SALARY >= ? AND SALARY <= ?";

  
//Returns the list of employees who fall into the given salary 
  
//range. The input parameter is the immutable map object 
  
//obtained from the HttpServletRequest. This is an early 
  
//refactoring based on "Introduce Parameter Object"

  
public List findBySalaryRange(Map salaryMap);
}

提供 DAO 实现类

定义好接口后,现在我必须提供具体的 Employee DAO 实现,EmployeeDAOImpl:
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import com.bea.dev2dev.to.EmployeeTO;

public class EmployeeDAOImpl implements IEmployeeDAO{

  
public List findBySalaryRange(Map salaryMap)
  {
    Connection conn 
= null;
    PreparedStatement pstmt 
= null
    ResultSet rs 
= null
    List empList 
= new ArrayList();
    
//Transfer Object for inter-tier data transfer
    EmployeeTO tempEmpTO = null;
    
try{
    
//DBUtil - helper classes that retrieve connection from pool
      conn = DBUtil.getConnection(); 
      pstmt 
= conn.prepareStatement(FIND_BY_SAL_RNG);
      pstmt.setDouble(
1, Double.valueOf( (String)
          salaryMap.get(
"MIN_SALARY") );
      pstmt.setDouble(
2, Double.valueOf( (String)
          salaryMap.get(
"MIN_SALARY") ); 
      rs 
= pstmt.executeQuery(); 
      
int tmpEmpNo = 0;
      String tmpEmpName 
= "";
      
double tmpSalary = 0.0D;
      
while (rs.next()){ 
        tmpEmpNo 
= rs.getInt("EMP_NO");
        tmpEmpName 
= rs.getString("EMP_NAME");
        tmpSalary 
= rs.getDouble("SALARY");
        tempEmpTO 
= new EmployeeTO(tmpEmpNo,
              tmpEmpName,
              tmpSalary);
        empList.add(tempEmpTO);   
      }
//end while 
    }//end try 
    catch (SQLException sqle){ 
      
throw new DBException(sqle); 
    }
//end catch 
    finally
      
try
        
if (rs != null){ 
          rs.close(); 
        }
      }
      
catch (SQLException sqle){
        
throw new DBException(sqle);
      }
      
try{
        
if (pstmt != null){
          pstmt.close(); 
        }        
      }
      
catch (SQLException sqle){
        
throw new DBException(sqle);
      }
      
try
        
if (conn != null){
          conn.close();
        }        
      }
      
catch (SQLException sqle){ 
        
throw new DBException(sqle);
      }
    }
//end of finally block
    return empList;
  }
//end method findBySalaryRange
}

以上代码阐明了 DAO 模式的几个关键要素:
1. 它们封装所有的 JDBC API 交互。如果用到了 Kodo 或 Hibernate 这些 O/R 映射工具,它们的 DAO 类将把这些私有 API 都封装好。
2. 它们利用与 JDBC API 无关的 transfer object(http://java.sun.com/blueprints/corej2eepatterns/Patterns/TransferObject.html) 封装获取的数据,并返回到业务层待进一步处理。
3. 它们是无状态的。每个业务对象的访问和改变持久化数据它们都单独对应。
4. 它们捕获任何错误(例如,数据库不可用,错误的 SQL 语法)并利用底层 JDBC API 或数据库的 SQLException 进行报告。接着 DAO 对象利用 JDBC 无关的提示再次向业务对象通知这些错误,比如定制 DBException 运行时异常类。
5. 它们释放 Connection 以及 PreparedStatement 这些数据库资源对象返回给连接池并释放已经使用过的 ResultSet 游标占用的内存。

因此,DAO 层抽象了底层数据访问 API,为业务层提供了一致的数据访问 API。

构建 DAO 工厂

DAO 工厂是典型的工厂设计模式,为业务对象构建和服务具体的 DAO 实现。业务对象使用 DAO 接口,所以并不知道具体的实现类。依赖倒置原则赋予了 DAO 工厂极大的灵活性。DAO 工厂可以轻松的改变 DAO 实现(例如从 JDBC 实现到基于 Kodo 的 O/R 映射实现)而不会影响到业务对象,不过一旦 DAO 接口约定建立将不能改变:
public class DAOFactory {
  
private static DAOFactory daoFac;

  
static{
    daoFac 
= new DAOFactory();
  }

  
private DAOFactory(){}

  
public DAOFactory getInstance(){
    
return daoFac;
  }

  
public IEmployeeDAO getEmployeeDAO(){
    
return new EmployeeDAOImpl();
  }
}

与业务组件协作

是时候来了解 DAO 是如何适应这样的环境了。前面说到 DAO 与业务组件协作获取和改变持久业务数据。下面列出了业务服务组件并展示了如何与 DAO 层交互:
public class EmployeeBusinessServiceImpl implements 
                                       IEmployeeBusinessService {

  
public List getEmployeesWithinSalaryRange(Map salaryMap){

    IEmployeeDAO empDAO 
= DAOFactory.getInstance()
                                    .getEmployeeDAO();
    List empList 
= empDAO.findBySalaryRange(salaryMap);
    
return empList;
  }
}

完美而清晰,不依靠包括 JDBC 在内的任何持久化接口。

问题

DAO 设计模式并不是没有缺点的:

代码重复:正如 EmployeeDAOImpl 类列出的,代码重复是基于 JDBC 的传统数据库访问的主要问题。反复书写代码明显违反了基本的 OO 代码复用原则。对于项目开销、时间和消耗都有着明显的不利因素。

耦合:DAO 代码非常紧密的和 JDBC 接口以及核心集合类型耦合在一起。这点可以从每个 DAO 类导入语句的数目体现。

资源泄漏:进入 EmployeeDAOImpl 类的设计,所有 DAO 方法都必须释放已获取的数据库资源的控制权,比如 connection、statements 以及结果集。这样做是很危险的,因为一名没有经验的程序员可以很容易的绕开这些程序块。结果资源将流失,最终导致系统当机。

错误捕获:JDBC 驱动通过抛出 SQLException 报告所有的错误情况。SQLException 是 checked exception。因此开发者被迫处理它——即使无法从大部分异常中恢复,这样也就导致了混乱的代码。此外,从 SQLException 对象获取错误代码和消息是数据库供应商特定的,因此不可能编写灵活的 DAO 错误消息代码。

脆弱的代码:在基于 JDBC 的 DAO 中,为 statement 对象绑定变量的设置,以及获取数据使用的结果集 getter 方法是频繁用到的两个任务。如果 SQL 语句中的列数改变了,或者列的位置改变了,代码将不得不再经过严格的反复修改、测试然后部署。

让我们来看看如何继承 DAO 的优势并去除上面提到的缺点。

进入 Spring DAO

在上面列出的问题可以通过改变某些代码来解决,接着再分离或封装遗留代码。Spring 的设计者做得非常严密,提供了拥有超轻量、健壮以及高度扩展性的 JDBC 框架。固定的部分(像获取连接、准备 statement 对象、执行查询、释放数据库资源这些)已经一次性提供好了——所以框架已经帮助我们排除了传统的基于 JDBC DAO 的缺点。

图 2 展示了主要的 Spring JDBC 框架组成板块。通过适当的接口,业务服务对象继续使用 DAO 实现类。JdbcDaoSupport 是 JDBC 数据访问对象的超类。它关联了实际的数据源。Spring 的控制反转(Inversion of Control,IoC)容器或者叫 BeanFactory 是用来获取具体的数据源配置并把这些细节关联给 JdbcDaoSupport。JdbcDaoSupport 类最重要的功能是为其子类构造可用的 JdbcTemplate 对象。

          图 2. Spring JDBC 框架的主要组件

JdbcTemplate 是 Spring JDBC 框架最重要的类。引用文档中的一段话:“它简化了 JDBC 的操作并帮助开发者避免一般性错误,它完成了核心 JDBC 工作流程,剥离了应用程序代码中提供的 SQL 语句和提取结果。”该类帮助分离 JDBC DAO 代码的静态部分并赋予了以下任务:

1. 从数据源获取连接。
2. 准备适当的 statement 对象。
3. 执行 SQL CRUD 操作。
4. 遍历结果集,并把结果迁移到标准的集合对象中。
5. 捕获 SQLException 异常并翻译成更加具体的错误体系。

用 Spring DAO 重写

现在你已经对 Spring JDBC 框架有了基本了解,该是重写现有代码的时候了。我分步骤进行,在这一过程中将探讨如何克服前面章节提到的问题。

第 1 步:修改 DAO 实现类—— EmployeeDAOImpl 现在继承自 JdbcDaoSupport 并获取 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); 
  }
}

上面代码中,传入参数 salaryMap 的值被存储在 double 数组中,参数的顺序按照 SQL 字符串中顺序一样。查询结果通过 queryForList() 方法返回包含 Map 对象(每列一条,使用列名作为 key)的 List(每行一条)。稍后我将说明如何返回一列 transfer objects。

通过简单的代码,明显可以发现 JdbcTemplate 是鼓励复用的,这将减少 DAO 实现中大量的代码。JDBC 与集合包之间紧密耦合也被去除了。JDBC 资源泄漏不再是问题,JdbcTemplate 确保数据库资源在使用后以固定的顺序被释放掉。

另外,在使用 Spring DAO 的时候不用把精力集中在捕获异常上面。JdbcTemplate 类捕获了 SQLException,接着基于 SQL 错误代码或错误状态翻译成 Spring 具体的异常。例如,当试图插入重复的值到主键列时抛出 DataIntegrityViolationException。当然,当你无法从错误中恢复时无需捕获这些异常。这是由于根异常类在 Spring DAO 中,DataAccessException 是运行时异常。值得注意的是 Spring DAO 异常与具体的数据访问实现无关。同样的异常也会在使用 O/R 映射方案实现的情况下抛出。

第 2 步:修改业务服务——通过 Spring 容器传递一个 DAO 实现类的引用,业务服务现在实现了 setDao() 新方法。这种处理叫做“设值注入”,并通过第三步中 Spring 容器的配置文件进行适配。注意这里不再需要 DAOFactory 了,Spring 提供 BeanFactory 代替:
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;
  }
}

想必你已经领略了 P2I 的灵活性;即使我修改 DAO 实现,也只是稍稍修改业务服务实现而已。这点小小的修改使业务服务被 Spring 容器管理起来了。

第 3 步:配置 Bean 工厂—— Spring bean 工厂需要一个配置文件初始化和启动 Spring 框架。这个配置文件把所有的业务服务和 DAO 实现类集成到 Spring bean 容器。除此以外,配置文件也包含了初始化数据源和 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>

通过调用 JdbcDaoSupport 的 setDataSource() 方法,Spring bean 容器为 DAO 实现设置数据源对象。同时也为业务服务提供 DAO 实现。

第 4 步:测试——最后写 JUnit 测试类。依照 Spring 体系,我将在容器以外进行测试。按照第 3 步中的配置文件,我将使用 WebLogic Server 连接池。
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);        
    }     
}

使用变量绑定

到目前为止,我已经演示了搜索最低工资和最高工资之间的雇员。让我们再假设某个情景,比如业务用户想让搜索范围颠倒一下。DAO 代码是如此脆弱以致于需要修改才能对应这种改变。这个问题是由于用到的位置变量绑定(由"?"表示)是静态的。Spring DAO 提供了以命名方式的变量绑定来化解这一问题。下列代码在 IEmployeeDAO 中引入了以命名方式的变量绑定(由:"<名称>"表示),注意查询部分的变化:
import java.util.Map;
public interface IEmployeeDAO {

  
//SQL String that will be executed
  public String FIND_BY_SAL_RNG = "SELECT EMP_NO, EMP_NAME, "
  
+ "SALARY FROM EMP WHERE SALARY >= :max AND SALARY <= :min";

  
//Returns the list of employees falling into the given salary range
  
//The input parameter is the immutable map object obtained from 
  
//the HttpServletRequest. This is an early refactoring based on 
  
//- "Introduce Parameter Object"

  
public List findBySalaryRange(Map salaryMap);
}

大部分的 JDBC 驱动只支持位置变量绑定。因此在运行时,Spring DAO 还是把查询转换为位置变量绑定,并设置适当的绑定值。为了完成这一操作,现在你要使用 NamedParameterJdbcDaoSupport 和  NamedParameterJdbcTemplate 类来代替 JdbcDaoSupport 和 JdbcTemplate。下面是修改后的 DAO 实现类:
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcDaoSupport;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;

public class EmployeeDAOImpl extends NamedParameterJdbcDaoSupport 
    
implements IEmployeeDAO{

  
public List findBySalaryRange(Map salaryMap){

    NamedParameterJdbcTemplate tmplt 
= 
                             
this.getNamedParameterJdbcTemplate();
    
return tmplt.queryForList(IEmployeeDAO.FIND_BY_SAL_RNG
                ,salaryMap); 
  }
}

NamedParameterJdbcDaoSupport 的 getNamedParameterJdbcTemplate() 方法返回一个已经由数据源预初始化的 NamedParameterJdbcTemplate 实例。Spring Beanfactory 从配置文件获取详细信息完成初始化操作。在执行的时候 NamedParameterJdbcTemplate 每次都委派 JdbcTemplate 进行从命名参数到位置占位符的操作。你会发现命名参数的引入使任何潜在的 SQL 语句的改变都不会影响到 DAO 方法。

最后,如果你使用的数据库不支持自动类型转换的话,JUnit 测试类中的 initSalaryMap() 方法要做点小调整。
private void initSalaryMap() {
        salaryMap 
= new HashMap();
        salaryMap.put(
"MIN_SALARY",new Double(1));
        salaryMap.put(
"MAX_SALARY",new Double(50));
    }

Spring DAO 回调(Callbacks)

到目前为止我已经介绍了如何使 JDBC 代码的静态部分在 JdbcTemplate 类中实现封装和通用特性来解决传统 DAO 设计的弊端。现在让我们把目光转移到变量方面,比如设置变量绑定以及遍历 ResultSet。尽管 Spring DAO 有了通用的解决方案,但在某些基于 SQL 的环境中,你可能要设置变量绑定。

通过把代码转换到 Spring DAO,我引入了一个微妙的运行时错误,这破坏了业务服务和客户端之间的契约。错误的源头可回溯到最初的 DAO,将不会返回一列 EmployeeTO 实例,而是通过 dbcTemplate.queryForList() 方法返回一列 map(每个 map 中存储的值都是结果集的行)。

正如你了解的,JdbcTemplate 是基于模板方法设计模式,利用 JDBC API 定义 SQL 执行流程。这个流程必须被修改以修正被破坏的契约。首选方案是在子类中修改/继承这个流程。你可以遍历 JdbcTemplate.queryForList() 返回的列(list)接着用 EmployeeTO 实例替换 map 中的对象。不过,将导致混合使用静态和动态代码,这是我想努力避免的。第二种方案是把代码加入多个流程来改变 JdbcTemplate 提供的钩子。这必须要封装 transfer object 的转换代码到不同的类中,接着通过钩子连接它。这样任何转换逻辑的改变不会引起 DAO 的改变。

很明显,第二种方案是首选,通过实现由 Spring 框架接口规范定义的方法的类来完成。这些方法被称作回调,并通过框架的 JdbcTemplate 来注册。当某些事件发生时(比如在框架无关的 transfer objects 中遍历和转换 ResultSet)这些方法会通过框架被调用。

第一步:transfer object

下面是你可能感兴趣的 transfer object。注意 transfer object 已经被修正了:
package com.bea.dev2dev.to;

public final class EmployeeTO implements Serializable{

      
private int empNo;   
      
private String empName;   
      
private double salary;

      
/** Creates a new instance of EmployeeTO */
      
public EmployeeTO(int empNo,String empName,double salary) {
          
this.empNo = empNo;
          
this.empName = empName;
          
this.salary = salary;
      }
      
public String getEmpName() {
          
return this.empName;
      }
      
public int getEmpNo() {
          
return this.empNo;
      }
      
public double getSalary() {
          
return this.salary;
      }
      
public boolean equals(EmployeeTO empTO){
          
return empTO.empNo == this.empNo;
      }
}

第二步:实现回调接口

RowMapper 是实现从结果集转换到 transfer object 的接口。例如:
package com.bea.dev2dev.dao.mapper;

import com.bea.dev2dev.to.EmployeeTO;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.springframework.jdbc.core.RowMapper;

public class EmployeeTOMapper implements RowMapper{

  
public Object mapRow(ResultSet rs, int rowNum) 
                                         
throws SQLException{
      
int empNo = rs.getInt(1);
      String empName 
= rs.getString(2);
      
double salary = rs.getDouble(3);
      EmployeeTO empTo 
= new EmployeeTO(empNo,empName,salary);
      
return empTo;
   }
}

注意实现类没有调用 ResultSet 对象的 next() 方法。这一细节已经由框架处理好了,它可以从结果集提取相应行的准确值。回调实现类产生的任何 SQLException 也会由 Spring 框架处理。

第三步:插入回调接口


通常,当 SQL 查询执行时,JdbcTemplate 利用默认的 RowMapper 实现类生成 map 列。现在需要注册定制的回调实现类来改变 JdbcTemplate 的行为。注意,我现在使用 NamedParameterJdbcTemplate 的 query() 方法来代替 queryForList() 方法:
public class EmployeeDAOImpl extends NamedParameterJdbcDaoSupport 
    
implements IEmployeeDAO{

  
public List findBySalaryRange(Map salaryMap){

    NamedParameterJdbcTemplate daoTmplt 
= 
          getNamedParameterJdbcTemplate();
    
return daoTmplt.query(IEmployeeDAO.FIND_BY_SAL_RNG, salaryMap,
          
new EmployeeTOMapper());
  }
}

Spring DAO 框架利用查询执行后返回的结果进行遍历。每一步的遍历,框架都调用 EmployeeTOMapper 类实现的 mapRow() 方法来进行从结果集的行转换到 EmployeeTO transfer object 的转换。

第四步:修改 JUnit 类

现在对返回的 transfer objects 进行测试,因此要修改测试方法。
public class EmployeeBusinessServiceImplTest extends TestCase {

  
private IEmployeeBusinessService empBusiness;
  
private Map salaryMap;
      List expResult;

      
// all methods not shown in the listing remain the 
      
// same as in the previous example
      private void initExpectedResult() {
          expResult 
= new ArrayList();
          EmployeeTO to 
= new EmployeeTO(2,"John",46.11);
          expResult.add(to);
      }

      
/**
       * Test of getEmployeesWithinSalaryRange method, of 
       * class com.bea.dev2dev.business.
       * EmployeeBusinessServiceImpl
       
*/
      
public void testGetEmployeesWithinSalaryRange() {
          List result 
= empBusiness.
        getEmployeesWithinSalaryRange(salaryMap);
          assertEquals(expResult, result);        
      }

      
public void assertEquals(List expResult, List result){
          EmployeeTO expTO 
= (EmployeeTO) expResult.get(0);
          EmployeeTO actualTO 
= (EmployeeTO) result.get(0);
          
if(!expTO.equals(actualTO)){
               
throw new RuntimeException("** Test Failed **");
          }     
      }
}

优势

Spring JDBC 框架的优势就是清晰,我示范了其重要的优势和如何把 DAO 代码减少到数行。代码变得不再脆弱,这得感谢框架所提供一步到位的以命名方式的变量绑定,还分离了 transfer object 迁移逻辑到 mapper 中。长久以来的资源泄漏问题和错误处理不再是首要问题了。

Spring JDBC 的优势是鼓励你把现有的代码迁移到该框架中。希望本文能尽可能的帮助你,让你了解一些工具和重构的知识。例如,如果你没有采用 P2I 提取接口,重构时只有由现有 DAO 实现类创建接口。除此以外,再请关注一下本文的附加参考信息。

下载

你可以在此下载本文中的源码。

结论

在本文中,首先我向大家讲解了数据访问对象(DAO)设计模式的基本概念,并从正反两方面进行讨论。Spring DAO、JDBC 框架的介入指出了传统 DAO 的缺点。接着,脆弱的 DAO 代码也被 Spring 框架命名参数的支持所修正。最后,回调特性示范了如何改变框架行为。

参考

J2EE 核心模式:数据访问对象(Sun 开发者网)——对 DAO 设计模式的详细描述

Spring DAO 框架——Spring DAO 官方文档

Spring 和 WebLogic Server 集成(Dev2Dev)——了解如何让 Spring 与 WebLogic Server 集成

WebLogic 8.1 数据源配制(文档)——该文档提供了利用管理控制台配制数据源的详细步骤

重构——该网站介绍了重构的基础知识和 Martin Fowler 书中关于重构细节的所有分类,重构:改善现有代码的设计;该网站也包括了一系列的重构工具。


请注意!引用、转贴本文应注明原译者:Rosen Jiang 以及出处:http://www.blogjava.net/rosen
 

 
原创粉丝点击