摘要
在J2EE 应用中,业务组件通常使用JDBC API
访问和改变关系数据库中的持久化数据。这样经常导致将持久化代码和业务逻辑混合在一起--- a bad idea. Data Access
Object (DAO) 设计模式通过把持久化逻辑分离到数据访问类中从而解决了这个问题。
本篇文章有关于DAO 设计模式的入门 ,重点放在DAO模式的优缺点。然后介绍了Spring 2.0 JDBC/DAO 框架并展示了它如何优雅的解决传统DAO 设计中的缺点。
传统DAO设计
按照J2EE 核心设计模式( Core J2EE Desgin Pattern) 这本书中的分类,Data Access
Object(DAO) 属于集成层设计模式。DAO在一个分开的层中封装了持久化存储访问和操作的代码。本篇文章中设计到的持久存储指的是RDBMS。
DAO模式是业务逻辑层和持久存储层之间的抽象层,例如图1. 业务对象通过数据访问对象访问RDBMS(data
source)。这个抽象层可以灵活的介入并简化了应用程序代码。理想情况下,当改变数据源,比如更换数据库厂商或者类型,只需要改变数据访问对象并对业
务对象的影响最小。
图1 使用DAO之前和之后的对比
现在我已经说明了基本的DAO设计模式,到了写一些代码的时候了.下面的例子是一个公司的域模型(domain
model).为了简单化,界定公司有若干工作在不同部门的雇员,比如销售,市场和人力资源.因为简化的原因,我将只关注一个单一的实体叫做雇员
“Employee”。
面向接口编程(Program to an Interface(P2I))
DAO设计模式提供的灵活性主要归因于一个对象设计的最佳实践(best
pratice):面向接口编程。这个规则是指具体对象必须实现一个接口,在调用程序中使用接口而不是具体对象本身。这样做,你可以很容易的代理到一个不
同的实现而对客户端的代码影响最小。
按照这个规则,我将定义Employee DAO 接口,IEmployeeDAO ,并定义findBySalaryRange()方法。业务组件和DAO的这个方法交互。
import java.util.Map;
public interface IEmployeeDAO {
//被执行的SQL 语句
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实现类
已经定义了DAO类,现在必须提供一个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方法的几个关键点:
封装了所有的同JDBC API的交互.如果有一个O/R mapping解决方案像Kodo或Hibernate被使用,DAO类可以包装这些具体产品的API。
包装了JDBC API 中立的查询对象,返回给业务层做进一步的处理。本质上他们是无状态的。他们单一的目标是供业务对象去访问和改变持久数据。在处理底层的JDBC API 或数据库时,他们捕获发生的任何错误(比如,数据库不可用,错误SQL 语法),并报告为SQLException.DAO对象把这种错误通知给业务对象使用JDBC中立的,定制构建的运行时异常类DBException。他们释放池中的数据库资源像Connection和PreparedStatement对象 ,在ResultSet使用完后,撤销ResultSet游标占有的内存。
因此,DAO层通过抽象底层的数据访问API,从而提供了一直的数据访问方式。
构建DAO Factory
DAO Factory 是典型的Factory
设计模式的实现,为的是让业务对象能创建和使用具体DAO的实现类。业务对象使用DAO接口而不关心其具体实现类。这种可以依赖倒置的原因是DAO
Factory提供了巨大的灵活性。只有DAO接口建立的约定保持不变,可以很容易的去改变DAO的实现(比如,从直接的JDBC
到基于Kodo的O/R Mapping),而不影响客户端的业务对象。
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;
}
}
这是很清晰的,根本没有任何对持久化接口的依赖。
问题
DAO设计模式也不是没有缺点:
代码重复:耦合:资源泄露:错误处理:脆弱代码:
让我们看看怎么保持DAO设计的优点而克服这些缺点呢?[/img]..