SPRING 数据访问对象 (DAO) 框架入门
页面: 1, 2, 3

Using Bind Variables

到目前为止,我们搜索了工资介于最低值和最高值之间的雇员。假设在某种情形下,业务用户想要颠倒这一范围。DAO代码很脆弱,将不得不通过更改来满足要求的变化。这个问题在于使用了静态的位置绑定变量(用“?”表示)。Spring DAO通过支持命名的绑定变量来挽救这个情况。修改的IEmployeeDAO清单引入了命名的绑定变量(用“:<some name>”表示)。注意查询中的变化,如下所示:

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。可见,使用命名的参数使得DAO方法不受底层SQL声明任何更改的影响。

最后,如果数据库不支持自动类型转换,需要如下所示,对JUnit测试类中的initSalaryMap()方法稍做修改。

private void initSalaryMap() {
        salaryMap = new HashMap();
        salaryMap.put("MIN_SALARY",new Double(1));
        salaryMap.put("MAX_SALARY",new Double(50));
    }

Spring DAO回调函数

至此,已经说明为了解决传统DAO设计中存在的问题,如何封装和概括JdbcTemplate类中JDBC代码的静态部分。现在了解一下有关变量的问题,如设置绑定变量、结果集遍历等。虽然Spring DAO已经拥有这些问题的一般化解决方案,但在某些基于SQL的情况下,可能仍需要设置绑定变量。

在尝试向Spring DAO转换的过程中,介绍了由于业务服务及其客户机之间的约定遭到破坏而导致的隐蔽运行时错误。这个错误的来源可以追溯到原始的DAO。dbcTemplate.queryForList()方法不再返回EmployeeTO实例列表。而是返回一个map表(每个map是结果集的一行)。

如您目前所知,JdbcTemplate基于模板方法设计模式,该模式利用JDBC API定义SQL执行工作流。必须改变这个工作流以修复被破坏的约定。第一个选择是在子类中更改或扩展工作流。您可以遍历JdbcTemplate.queryForList()返回的列表,用EmployeeTO实例替换map对象。然而,这会导致我们一直竭力避免的静态代码与动态代码的混合。第二个选择是将代码插入JdbcTemplate提供的各种工作流修改钩子(hook)。明智的做法是在一个不同的类中封装传输对象填充代码,然后通过钩子链接它。填充逻辑的任何修改将不会改变DAO。

编写一个类,使其实现在Spring框架特定的接口中定义的方法,就可以实现第二个选择。这些方法称为 回调函数,通过JdbcTemplate向框架注册。当发生相应的事件(例如,遍历结果集并填充独立于框架的传输对象)时,框架将调用这些方法。

第一步:传输对象

下面是您可能感兴趣的传输对象。注意,以下所示的传输对象是固定的:

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接口,填充来自结果集的传输对象。下面是一个例子:

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传输对象填充最终结果的每一行。

第四步:修改后的JUnit类

现在要根据返回的传输对象测试这些结果。为此要对测试方法进行修改。

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方法简化到只有几行代码。代码不再脆弱,这要感谢该框架对命名的参数绑定变量的“开箱即用”支持,以及在映射程序中将传输对象填充逻辑分离。

Spring JDBC的优点应该促使您向这一框架移植现有的代码。希望本文在这一方面能有所帮助。它会帮助您获得一些重构工具和知识。例如,如果您没有采用P2I Extract Interface,那么可以使用重构,从现有的DAO实现类创建接口。除此之外,查看本文的参考资料可以得到更多指导。

下载

  • 可以下载本文用到的源代码。

结束语

在此篇文章中,我讲述了数据访问对象(DAO)设计模式的基础知识,并从正反两方面进行了讨论。引入Spring DAO或JDBC框架来克服传统DAO的不足。然后,根据Spring框架提供的“开箱即用”命名参数支持对脆弱的DAO代码进行了改进。最后,回调功能展示了如何在指定点修改框架行为。

参考资料

  • Core J2EE Patterns: Data Access Object(Sun开发人员网络)- 提供了DAO设计模式的详细描述
  • Spring DAO Framework - 官方Spring DAO文档
  • Refactoring - 重构基础知识讲解和Martin Fowler撰写的 Refactoring: Improving the Design of the Existing Code一书中所有重构详细资料的目录;该站点还包含重构使用的工具列表

Dhrubojyoti Kayal是Capgemini Consulting的高级顾问。在利用企业Java技术开发和设计应用程序和产品方面,拥有5年以上的经验。