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

摘要

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

 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值