最近参与WEB编程项目,采用STRUTS框架,在处理到数据持久化的时候决定采用DAO设计模式,因此读了SUN的J2EE 核心设计模式中DAO设计模式,现翻译为中文于大家共享,不足与错误指出望大家指出,也愿该文对大家有所帮助.
在JAVA编程的时候, 有时候看起来非常直接的实现却非要用设计模式转若干个弯去实现他, 这似乎显的很多余,但是采用一些成熟的设计模式,会使程序更加的健壮,松耦合以及好维护和扩展.
DAO 设计模式
背景:
根据数据源的不同,访问数据的方法也会有所不同,访问持久化的数据源,比如数据库,也会由于其存储类型的不同(关系数据库,面向对象的数据库,简单文件储存,其他方式)和提供商自定义数据类型的不同而有很大的区别。
出现的问题:
许多投入使用的,J2EE WEB 应用程序在一些时候需要进行数据的持久化. 对于很多的WEB应用,数据的持久化存储可以通过不同的机制来实现,文档中清楚的标出了这些用于访问不同数据持久机制的API的不同之处. 还有一些其他的应用或许会访问一些位于特有系统上的数据资源.比如,在大型机的系统之上,也可能在轻量级的目录访问协议LDAP仓库中,或者是其他什么系统. 还有就是,数据也可能是由诸如B2B这样的外部集成系统服务,信用卡局服务,或者其他服务来提供的.一般来说,程序使用一些共享的分布式组件来表示持久化数据.比如实体BEAN. 当一个程序中实体BEAN以比较直接的方式访问持久化数据时大多会考虑采用BEAN管理持久化方式(BMP)说明白点,就是程序中的实体BEAN包含有直接访问持久化数据的代码.另外一种情况,程序可以采用容器管理持久化,你不需要写任何代码,而是让容器自己来处理数据持久化的具体细节.
程序可以使用JDBC API 来访问位于关系数据库中的数据. 他使得在诸如关系型数据库这样的持久化载体中,对数据进行标准的访问和处理成为可能. 也使J2EE应用程序可以使用SQL语句作为标准的访问关系型数据库语句. 然而,即便是都是关系型数据库的环境下,由于不同的数据库产品,也会导致SQL在使用上,语法和格式也各不相同.
对于不同类型的数据持久化仓库,差异甚至会更大. 访问机制,API,以及一些其他特性,会因为他们是关系型数据库,面向对象型数据库还是一般的文件而大相径庭.需要访问以前遗留下来的系统或者诸如大型主机,B2B这样的专业系统中数据库的应用程序,会经常使用到一些自己特有的API. 这些特有的数据源对应用程序的编写提出了很大的挑战,而且很有可能在编写的过程中造成程序代码和数据访问代码间产生相互依赖性.当商业组件诸如:实体BEAN,会话 BEAN,以及servlets和JSP帮助对象这样的表示组件需要访问数据资源的时候,可以用标准的API来实现其数据库的连接和数据的具体操作.但是,如果把连接数据库和数据的操作代码和这些组件写在一起的话,会导致这些组件和数据库操作之间的耦合,这种耦合的存在,使得在应用程序中从当前数据源类型迁移到另一种数据源类型变的十分困难和乏味. 如果数据源改变了,那么你的组件也不得不改变来适应新的数据源.
必要性:
1 像 bean管理实体bean, 会话 bean, servlets, 以及其他一些像jsp帮手对象这样的组件,通常需要从持久化的数据库或者原先遗留下来的系统以及 B2B, LDAP这样的系统中提取或存储数据。
2 用于持久化储存的API因他们的提供商的不同而各自不同。还有一些的数据源也可能有自己的一些特有的API或者是一些非标准的API。众多类型的数据持久化系统和载体,比如: 关系型数据库,面向对象数据库管理系统,XML文档,简单文件等等,使得API各不相同和性能各异。我们缺乏一种统一的API来对以上的不同的系统或者文件载体进行操作。
3 组件通常使用特有的API从内部系统或者是遗留下来的系统来访问或是提取数据。
4 当某些特定的访问机制和API包含在这些组件中的时候,将直接影响他们的兼容性。
5 组件需要对现有的持久化储存系统或者数据源的实现足够透明,以便在向不同的产品,不同类型的储存系统,和不同类型数据源中进行迁移的时候,变的简单。
解决方案
使用数据访问对象来抽象和封装对数据源的所有访问。数据访问对象负责管理与数据源的连接,来获取和储存其中的数据。
数据访问对象实现与数据源相关的访问机制。 数据源可以是关系型数据库管理系统,可以是像B2B EXCHANGE这样的内部服务,可以是LDAP库,或者也可以是通过CORBA IIOP 或者是低层sockets来访问的商业服务. 依赖于DAO的商业组件只对他的客户端暴露一些非常简单的DAO外部接口. DAO将数据源的实现细节对客户端完全的隐藏了起来. 因为,暴露给客户端的DAO接口在低层数据源的实现发生改变时并不会随着改变,所以这种设计模式使得DAO可以适应不同的数据储存方式类型而不影响客户端和商业组件.最主要的, DAO还在组件和数据源之间扮演着协调者的角色.
以下是DAO设计模式中各个模块的解释:
1 BusinessObject指的是数据客户端,他通常需要去访问数据源以获得数据或储存数据.一个BusinessObject除了访问数据源的servlet或者helper bean之外也可以是会话BEAN,实体BEAN以及一些其他的JAVA对象.
2 DataAccessObject 是这个设计模式的核心部分, DataAccessObject为BusinessObject抽象了底层的数据访问实现的细节,使得访问数据变得透明. BusinessObject还将数据的装载和储存交给了DataAccessObject进行代理.
3 DataSource他表示的是数据源的实现. 一个数据源可以四像关系型数据库管理体统这样的数据库,可以是面向对象型的数据库管理系统,可以是XML文档,也可以是简单文件等等. 当然他也可以是其他的系统,(遗留系统,大型主机),可以是服务(B2B服务,信用卡局服务)或者是像LDAP这样的数据库等.
4 TransferObject他代表的是传递对象,一般用于数据的载体. DataAccessObject使用传递对象来将数据返回给客户端. DataAccessObject也可以使用传递对象来从客户端接受数据来将原先数据库中的数据进行更新.
策略:
由于每一个BusinessObject都有着相应的DAO,所以在BusinessObject,DAO,和底层实现之间是可以建立起确定的关系的(比如在关系型数据库中的表格)。一旦他们之间的关系建立了,我们就可以为这个应用使用专门定制出代码生成器生成涉及到该应用所有的DAO的代码。产生DAO的元数据还可以通过开发人员定制的描述符文件来获得.代码生成器也可以通过自动的对数据库进行检查,然后按照实际情况来提供一些必要的DAO来访问数据库. 如果对于DAO的要求过于复杂,则考虑使用第三方工具来为关系型数据库提供对象到关系的映射.这些工具一般都包含图形化的用户界面可以方便的将商业对象影射到持久化对象上,于是就可以定义DAO了。这些工具可以自动在影射一结束就自动的产生代码,不但如此,他门可以提供一些附加的好处,比如结果缓存,查询缓存,与应用服务器的整合,于第三方产品的整和(分布试缓存)等等.
1 工厂模式策略:
DAO设计模式可以通过采用抽象工厂和工厂方法模式来边的非常的灵活.
当底层数据储存实现不需要发生改变时,该策略可以使用工厂方法设计模式实现,来产生应用中所需的DAO.
当底层数据储存实现不得不发生变化的时候, 我们可以用抽象工厂模式来实现这个策略. 就象在设计模式:可重用面向对象软件的元素这本书中建议的那样,抽象工厂先创建然后再使用工厂方法的实现. 在当前情况,该策略可以提供一个抽象的DAO工厂对象(抽象工厂),用他来创建不同类型的具体DAO工厂.,每一个工厂都各自支持一种不同的数据持久化储存的实现. 一旦你为某个特定的实现获得了具体的DAO工厂,你则可以用这个工厂来产生那个特定实现所支持和实现的DAO对象.
优点与缺点:
DAO设计模式带来的好处.
1 透明化:
商业对象可以在完全不知道数据源如何具体实现的情况下来使用数据源. 访问数据源是透明的,因为实现细节已经被隐藏进了DAO.
2 迁移简单化:
DAO 层的出现,使得应用程序向不同的数据库实现进行迁移变的容易.商业对象可以对底层数据实现一无所知.这样,迁移只涉及到了对DAO层的修改. 另外,如果使用工厂策略,则使为每一种底层数据实现提供一个具体的工厂实现成为可能.在这种情况下,迁移到一种不同的数据实现,其实就相当于为这个应用程序再提供一个新的工厂实现.
3 减少在商业对象中的编程难度.
由于DAO管理着所有的数据访问细节,因而大大简化了在商业对象和其他使用DAO的数据客户端里的代码.所有的实现细节相关的代码比如(SQL 语句)都包含在DAO而不在商业对象中. 这样使得代码变的更加健壮而且大大提高了开发效率.
4 将所有的数据访问都单独集中到一层中去.
因为所有的数据访问操作现在都已经被DAO所代理,所以这个单独的数据访问层可以被看作可以是将数据访问实现和其余应用程序相互隔离的一层. 这样的集中,使得应用程序可以更加容易的来维护和管理.
缺点:
5 对容器管理持久化无用
由于EJB容器使用CMP(容器管理持久化)来管理实体BEAN. 容器会自动的为持久化储存访问提供服务.应用程序使用容器管理的实体BEAN则不需要 DAO层的参与.因为应用程序服务器本身就可以透明的提供这些功能.然而,DAO在组合式CMP和BMP需要的场合下还是有用的.
6 增加了多余的层.
由于DAO在数据客户端和数据源之外多创建了一层对象,因而,需要对他进行设计和实现,来均衡这个设计模式的利弊. 但是,一般来说,采用此设计模式还是利大于弊的.
7 需要对类的相互继承关系进行设计.
当使用工厂策略的时候,具体工厂类的继承关系和由这些工厂类生成的产品需要进行设计和实现. 我们需要仔细考虑这些多付出的工作是否真的可以产生出来更高的灵活性. 使用这个策略会使设计变的更加复杂,然而,你可以先从工厂方法模式开始来实现这个策略,然后在需要的情况下再转向抽象工厂
范例说明:
为DAO实现工厂类的策略
1 采用工厂方法设计模式
如果一个DAO 工厂只为一个数据库的实现,(比如ORACLE)而创建很多的DAO的时候,实现该策略时,我们考虑采用工厂方法设计模式. 假设该工厂类创建了CustomerDAO, AccountDAO, OrderDAO 等一些对象。
2 使用抽象工厂设计模式:
如果考虑为三种不同类型的数据库来实现这个策略,我们可以考虑采用抽象工厂设计模式. 假设. 这个工厂创建了CustomerDAO, AccountDAO, OrderDAO的一系列的DAO, 该策略运用了在抽象工厂中产生的工厂类中的工厂方法的实现.
代码说明:
以下代码举例说明了DAO设计模式的具体实现:
我们以使用抽象工厂的设计模式来对付多种类型数据库为例,在以下的例子中只具体列出CLOUDSCAPE 数据库类型的DAO设计模式的具体实现,其他类型数据库DAO设计模式的实现大同小异.
1 // Abstract class DAO Factory
public abstract class DAOFactory {
// List of DAO types supported by the factory
public static final int CLOUDSCAPE = 1;
public static final int ORACLE = 2;
public static final int SYBASE = 3;
...
// There will be a method for each DAO that can be
// created. The concrete factories will have to
// implement these methods.
// 所有实现该抽象工厂的工厂类中必须有的方法,用这些方法来创建具体的DAO类.
public abstract CustomerDAO getCustomerDAO();
public abstract AccountDAO getAccountDAO();
public abstract OrderDAO getOrderDAO();
//该抽象类的静态方法,用他来创建其他具体的DAO工厂类
public static DAOFactory getDAOFactory(
int whichFactory) {
switch (whichFactory) {
case CLOUDSCAPE:
return new CloudscapeDAOFactory();
case ORACLE :
return new OracleDAOFactory();
case SYBASE :
return new SybaseDAOFactory();
...
default :
return null;
}
}
}
2 以下是Cloudscape DAO FACTORY 类的实现,在他里面实现了该类型数据库的连接,以及实现了他所继承的抽象工厂类中所必须实现的那些方法,在这些方法中创建具体的DAO对象.
// Cloudscape concrete DAO Factory implementation
import java.sql.*;
public class CloudscapeDAOFactory extends DAOFactory {
public static final String DRIVER=
"COM.cloudscape.core.RmiJdbcDriver";
public static final String DBURL=
"jdbc:cloudscape:rmi://localhost:1099/CoreJ2EEDB";
// method to create Cloudscape connections
//建立Cloudscape 连接
public static Connection createConnection() {
// Use DRIVER and DBURL to create a connection
// Recommend connection pool implementation/usage
}
//创建 CustomerDAO 对象 当然返回的是一个该类实现的接口,他的好处就是实现了实现细节的隐蔽.
public CustomerDAO getCustomerDAO() {
// CloudscapeCustomerDAO implements CustomerDAO
return new CloudscapeCustomerDAO();
}
//创建 AccountDAO 对象 当然返回的是一个该类实现的接口,他的好处就是实现了实现细节的隐蔽.
public AccountDAO getAccountDAO() {
// CloudscapeAccountDAO implements AccountDAO
return new CloudscapeAccountDAO();
}
//创建 OrderDAO 对象 当然返回的是一个该类实现的接口,他的好处就是实现了实现细节的隐蔽.
public OrderDAO getOrderDAO() {
// CloudscapeOrderDAO implements OrderDAO
return new CloudscapeOrderDAO();
}
...
}
3 以下代码就是具体DAO类实现的接口也就是CloudscapeCustomerDAO()实现的接口: CustomerDAO .在该接口中定义了所有的业务方法.
// Interface that all CustomerDAOs must support
public interface CustomerDAO {
public int insertCustomer(...);
public boolean deleteCustomer(...);
public Customer findCustomer(...);
public boolean updateCustomer(...);
public RowSet selectCustomersRS(...);
public Collection selectCustomersTO(...);
...
}
4 以下CloudscapeCustomerDAO类实现的具体业务细节和数据操作细节, 他是要向客户数据端隐蔽的.
import java.sql.*;
public class CloudscapeCustomerDAO implements
CustomerDAO {
public CloudscapeCustomerDAO() {
// initialization
}
// The following methods can use
// CloudscapeDAOFactory.createConnection()
// to get a connection as required
public int insertCustomer(...) {
// Implement insert customer here.
// Return newly created customer number
// or a -1 on error
}
public boolean deleteCustomer(...) {
// Implement delete customer here
// Return true on success, false on failure
}
public Customer findCustomer(...) {
// Implement find a customer here using supplied
// argument values as search criteria
// Return a Transfer Object if found,
// return null on error or if not found
}
public boolean updateCustomer(...) {
// implement update record here using data
// from the customerData Transfer Object
// Return true on success, false on failure or
// error
}
public RowSet selectCustomersRS(...) {
// implement search customers here using the
// supplied criteria.
// Return a RowSet.
}
public Collection selectCustomersTO(...) {
// implement search customers here using the
// supplied criteria.
// Alternatively, implement to return a Collection
// of Transfer Objects.
}
...
}
5 下面的代码是数据客户端向DAO中传输数据的, 他其实就是一个JAVABEAN;
public class Customer implements java.io.Serializable {
// member variables
int CustomerNumber;
String name;
String streetAddress;
String city;
...
// getter and setter methods...
...
}
6最后就是客户数据端对这个设计的应用:
...
// create the required DAO Factory
DAOFactory cloudscapeFactory =
DAOFactory.getDAOFactory(DAOFactory.DAOCLOUDSCAPE);
// Create a DAO
CustomerDAO custDAO =
cloudscapeFactory.getCustomerDAO();
// create a new customer
int newCustNo = custDAO.insertCustomer(...);
// Find a customer object. Get the Transfer Object.
Customer cust = custDAO.findCustomer(...);
// modify the values in the Transfer Object.
cust.setAddress(...);
cust.setEmail(...);
// update the customer object using the DAO
custDAO.updateCustomer(cust);
// delete a customer object
custDAO.deleteCustomer(...);
// select all customers in the same city
Customer criteria=new Customer();
criteria.setCity("New York");
Collection customersList =
custDAO.selectCustomersTO(criteria);
// returns customersList - collection of Customer
// Transfer Objects. iterate through this collection to
// get values.
1 创建一个抽象工厂类,他包含两个重要的部分: 第一部分是 一些抽象方法,这些方法是所有实现该抽象工厂的具体工厂类所必须实现的. 第二部分 就是一个静态方法,该方法来创建一个具体类型数据源的工厂对象,比如文中的CloudscapeDAOFactory().
2 然后,分别创建各个类型数据源的工厂类,(本文以CloudscapeDAOFactory为例).在这个工厂类中里面也有两个重要组成部分: 第一部分就是实现在他继承的那个抽象工厂类中的左右抽象方法,在该方法中创建具体的DAO对象(这些对象的类在第4不具体定义实现),本文中三个方法分别创建了3 个具体的DAO对象,当然为了实现细节的隐蔽,这些方法返回的是这些具体DAO类门实现的接口(这些接口在第3步实现).
3 定义具体DAO类的接口,并在接口中定义所有的业务方法,和数据操作方法.
4 定义具体的DAO类,在这个类中才是实际的业务方法,和数据的操作的实现.
5 定义数据传输对象,他是用来在客户端和DAO之间传递数据的,他其实就是一个JAVABEAN.
6 完成以上5步之后我们就可以在数据客户端使用以上由DAO设计模式定义好的各个类了(见最后一个代码例子块).
以上6步大家在编程的时需具体体会,一般来说,数据库中的一个表就可以对应一个数据传递类也就是在第4步中定义的那个类,类中的属性就是表中的字段,然后加上相应的GET,SET 方法. 然后再按模式和以上步骤来定义具体的类.