核心J2EE模式-数据访问对象(DAO)

时间:2022-01-19 17:06:41

原文链接: http://java.sun.com/blueprints/corej2eepatterns/Patterns/DataAccessObject.html


核心J2EE模式 - 数据访问对象(Data Access Object, DAO)

背景

针对不同的数据源来访问数据。持久存储(如数据库)的访问是多变的,它取决于存储的类型(关系型数据库、面向对象的数据、纯文件等等)和供应商的实现。

问题

在某些时候,许多现实世界中的Java 2企业平台(Java 2 Platform Enterprise Edition,J2EE)应用程序需要使用持久数据。应用程序实现持久存储的机制不尽相同,而访问不同的持久存储机制的API有着显著的差异。一些应用程序也要访问属于独立系统的数据,例如:数据可能属于大型主机系统,或轻量级目录访问协议(Lightweight Directory Access Protocol,LDAP)数据库等。另外,数据可通过外部系统的服务来提供,如企业对企业(B2B)的集成系统、信用卡局服务等。

通常情况下,应用程序使用共享的分布式组件(如实体bean)代表持久数据。当实体bean显式地(explicitly)访问持久存储时(包含直接访问持久存储代码的实体bean),应用程序就被认为是采用bean管理持久性(Bean-managed Persistence,BMP)。使用较简单请求的应用程序,可能会放弃使用实体bean,代之的是使用会话bean或servlet直接访问持久化存储,以检索和更新数据。或者,应用程序可以采用容器管理持久性(Container-managed Persistence,CMP)的实体bean,从而让容器处理事务和持久化的细节。

应用程序可以使用JDBC API来访问关系型数据库管理系统(Relational Database Management System,RDBMS)的数据。JDBC API在持久存储方面,支持标准的数据访问和数据操作,如关系型数据库。JDBC API支持J2EE应用程序使用SQL语句-访问RDBMS的表的标准方法。然而,即使在RDBMS环境中,SQL语句的实际的语法和格式,可能因特定的数据库产品而有所不同。

不同类型的持久存储,会有更大的差异。在不同类型的持久存储之间(如RDBMS、面向对象的数据库、纯文件等等),访问机制(支持的API)和特征存在差异。那些需要访问来自于传统的或不同的系统(如主机,或B2B服务)的应用程序,时常被要求使用可能是专有的API。这些不同的数据源向应用程序提出了挑战,可能造成应用程序代码和数据访问代码之间的直接依赖(direct dependency)。当业务组件——实体bean、会话bean、甚至像servlet和Java服务端网页(JavaServer Pages,JSP)的辅助对象的展示组件,需要访问数据源时,可使用适当的API来实现数据源的连接和操作。但,在组件中引入连接和数据访问代码,将导致这些组件和数据源实现间的紧耦合。组件间的这种代码依赖关系,使得应用程序在不同的数据源类型间的移植变得困难和冗长。当数据源改变时,组件需要相应的改变以处理新类型的数据源。

Forces

  • 组件,如bean管理(bean-managed)的实体bean、会话bean、servlet和其他对象(如JSP页面辅助对象),会从持久仓库和其它数据源(如传统系统、B2B、LDAP等等)检索和存储信息。

  • 持久存储API的变化取决于产品供应商。数据源可能使用非标准和/或专有的API。这些API的差异及其兼容性的差异,取决于存储的类型:关系型数据库、面向对象的数据库管理系统(OODBMS)、XML文件、纯文件等等。目前缺乏一组统一的API,以解决访问这些不同的系统的需求。

  • 组件通常使用专有的API,来访问外部和/或传统系统,进行数据的检索和存储。

  • 当一个组件包含了特定的访问机制和API时,其可移植性直接受到影响。

  • 对实际的持久仓库或数据源实现,组件应该是透明的,便于在不同厂商的产品、不同的存储类型和不同的数据源类型间移植。

解决方案

使用数据访问对象(Data Access Object,DAO)来抽象和封装所有的数据源访问。DAO管理数据源的连接、数据的获取和数据。

DAO实现了与数据源交互所要求的访问机制。数据源可能是一个像RDBMS的持久仓库,一个像B2B交互的外部服务,一个像LDAP的数据库,或通过CORBA的因特网ORB互通协议(Internet Inter-ORB Protocol,IIOP)或低级别的套接字访问业务服务的业务服务。依赖于DAO的业务组件,使用DAO向客户端暴露的简单接口。DAO向其客户端完全隐藏了数据源的实现细节。因为DAO暴露给客户端的接口,不随底层数据源实现的改变而改变,这种模式允许DAO适应不同的存储方案,而不会影响其客户端或业务组件。从本质上讲,DAO充当组件和数据源之间的适配器。

构造

图9.1展示的类图,表示DAO模式的类关系。

核心J2EE模式-数据访问对象(DAO) 
图9.1Data Access Object

参与者及职责

图9.2中包含的时序图,展示在此模式下各参与者之间的相互作用。

核心J2EE模式-数据访问对象(DAO) 
图9.2数据访问对象(DAO)的时序图

BusinessObject

BusinessObject表示数据的客户端——访问数据源以获取和存储数据的对象。BusinessObject的实现,可能是会话bean、实体bean或其他的Java对象,不能是访问数据源的servlet或辅助bean。

DataAccessObject

DataAccessObject是此模式的主要对象。DataAccessObject为BusinessObject抽象出基本的数据访问实现,以符合数据源访问的透明性(transparent)。BusinessObject也委托DataAccessObjec进行数据的加载操作和存储操作。

DataSource

DataSource代表数据源的实现。数据源,可能是一个数据库(如RDBMS、OODBMS、XML数据库、纯文件系统等),也可能是另一个系统(传统/大型主机)、服务(B2B服务或信用卡局)或某种资源库(LDAP)。

TransferObject

TransferObject表示作为数据载体的传输对象。DataAccessObject使用TransferObject向客户端返回数据,收取来自于客户端的TransferObject中的数据以更新数据源的数据。

策略

自动DAO代码生成策略

既然每个BusinessObject对应一个具体的DAO,在BusinessObject、DAO和底层实现(如在RDBMS中的表)之间建立对应关系就是可行的。一旦建立了关系,写一个简单的应用程序指定的代码生成工具,以生成应用程序所需的所有DAO代码,也是可行的。用以生成DAO的源数据,可以来自开发人员定义的描述文件。此外,代码生成器可以从内部自动检查数据库,并提供访问数据库所必需的DAO(就是反向工程,lihf)。如果DAO的要求十分复杂,可以考虑使用第三方工具,为RDBMS数据库提供对象-关系映射(object-to-relational mapping,ORM)。这些工具通常包括GUI工具,把BussinessObject映射到持久化存储对象,从而定义了中间的DAO。一旦工具自动完成了映射代码的生成,就可提供其他增值功能,如结果缓存、查询缓存、应用程序服务器的集成、与其他第三方产品的集成(例如,分布式缓存)等等。

DAO策略的工厂

DAO模式,通过采用抽象工厂模式[GoF]和工厂方法模式[GoF],来达到高度灵活(请参阅本章中的“相关模式”)。

当底层存储不受实现变化的影响时,DAO策略应使用工厂方法模式,产生应用程序所需的大量的DAO。类图如图9.3所示。

核心J2EE模式-数据访问对象(DAO) 
图9.3使用工厂方法模式的DAO策略的工厂

当底层存储受实现变化的影响,DAO策略应使用抽象工厂模式实现。抽象工厂能反过来构建在工厂方法实现之上或使用工厂方法实现,由设计模式:可复用面向对象软件的元素 [GoF]建议。在这种情况下,DAO策略提供了一个抽象的DAO工厂对象(抽象工厂),可以建造各类具体的DAO工厂,每个工厂支持不同类型的持久存储的实现。一旦你获得了指定实现的具体的DAO工厂,就用它来产生这个指定实现所支持和实现的DAO。

此策略的类图如图9.4所示。类图上可见一个基本的DAOFactory,一个抽象类,由不同的具体的DAO工厂继承并实现,以支持数据库实现所指定的访问。客户端可以获得一个具体的DAO工厂实现(如RdbDAOFactory),并用它来取得伴随指定存储实现的具体的DAO。例如,数据客户端可以获取一个RdbDAOFactory,用它来得到具体的DAO,如RdbCustomerDAO、RdbAccountDAO等。DAO可以继承并实现一个通用的基类(如DAO1和DAO2所示),具体描述的DAO,用以满足它所支持的BusinessObject的要求。每一个具体的DAO负责数据源的链接、它所支持的业务对象的数据的获取和操作。

在本章的“示例代码”部分,展示了DAO模式及其策略的示例实现。

核心J2EE模式-数据访问对象(DAO) 
图9.4使用抽象工厂模式的DAO策略的工厂

如图9.5所示,表述此策略交互的时序图。

核心J2EE模式-数据访问对象(DAO) 
图9.5使用抽象工厂模式的DAO的工厂的时序图

影响

  • 透明化
    业务对象使用数据源时,不必了解数据源实现的具体细节。访问是透明的,因为实现细节屏蔽在DAO之中了。

  • 简化移植
    DAO层使应用程序在不同的数据库实现间移植变得容易。业务对象不需了解底层数据实现,因此,移植只涉及DAO层的变化。此外,如果采用了工厂策略,就可以为每一个底层存储实现提供一个具体的工厂实现。在这种情况下,移植到不同的存储实现意味着向应用程序提供一个新的工厂实现。

  • 减少业务对象的代码复杂度
    因为DAO管理所有数据访问的复杂度,简化了业务对象和其他使用DAO的数据客户端的代码。所有实现相关的代码(如SQL语句)都包含在DAO中,而不在业务对象中。这提高了代码的可读性和开发的产品性。

  • 把所有数据访问集中到单独的一层
    因为所有的数据访问操作都已集中到DAO——单独的数据访问层中,即:把数据访问实现从应用程序中分割出来的一层。这种集中化使应用程序易于维护和管理。

  • 对CMP无用
    因为EJB容器使用CMP管理实体bean,容器可以自动服务所有持久化存储的访问。使用容器管理实体bean的应用程序并不需要DAO层,因为应用程序服务器显然已提供了这种功能。然而,当需要CMP(即实体bean)和BMP(即会话Bean、servlet)结合时,DAO仍然是有用的。

  • 增加了额外的分层
    DAO在数据客户端和数据源之间建立了附加层,需要设计并实现它才能享受到DAO模式的优势。选择这种方法所带来的优势,需要额外的工作才能兑现。

  • 需要类层次设计
    使用工厂策略时,由工厂创建的具体工厂的层次结构和具体产品的层次结构,需要设计并实现。要权衡一下,是否有足够的理由,为了保证灵活性而做这些额外的工作。这增加了设计的复杂性。或者,你可以先从工厂方法模式出发,实现工厂策略,有必要的话,再转向抽象工厂模式。

示例代码

实现DAO模式

一个表示Customer信息的持久化对象的示例DAO,如示例9.4所示。findCustomer()方法被调用时,CloudscapeCustomerDAO创建一个Customer Transfer Object

使用DAO的示例代码如示例9.6所示。在这个例子中的类图如图9.6所示。

核心J2EE模式-数据访问对象(DAO) 
图9.6实现DAO模式

实施DAO策略的工厂

使用工厂方法模式

考虑一个例子,我们实现这样一个策略,一个DAO工厂为一个单一的数据库实现(如Oracle)产生许多DAO。工厂产生如下的DAO:CustomerDAO、AccountDAO、OrderDAO等等。此例的类图如图9.7所示。

核心J2EE模式-数据访问对象(DAO) 
图9.7使用工厂方法模式的的DAO策略的工厂实现

DAO工厂(CloudscapeDAOFactory)的示例代码在示例9.2中列出。

使用抽象工厂模式

考虑一个例子,我们正在考虑为三个不同的数据库实现此策略。在这种情况下,可以采用抽象工厂模式。此例的类图如图9.8所示。在例9.1中的代码,展示了抽象DAOFactory类的代码摘录。此工厂产生如下DAO:CustomerDAO、AccountDAO、OrderDAO等。此策略,在由抽象工厂产生的工厂实现中,使用工厂方法模式。

核心J2EE模式-数据访问对象(DAO) 
图9.8使用抽象工厂的DAO策略的工厂实现

示例9.1抽象DAOFactory类

// 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.
public abstract CustomerDAO getCustomerDAO();
public abstract AccountDAO getAccountDAO();
public abstract OrderDAO getOrderDAO();
...

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;
}
}
}

CloudscapeDAOFactory示例代码如示例9.2所​​示。OracleDAOFactory和SybaseDAOFactory的实现是相似的,除了各自实现的细节,如JDBC驱动程序、数据库URL、SQL语法的差异。

示例9.2Cloudscape的具体DAOFactory实现

// 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
public static Connection createConnection() {
// Use DRIVER and DBURL to create a connection
// Recommend connection pool implementation/usage
}
public CustomerDAO getCustomerDAO() {
// CloudscapeCustomerDAO implements CustomerDAO
return new CloudscapeCustomerDAO();
}
public AccountDAO getAccountDAO() {
// CloudscapeAccountDAO implements AccountDAO
return new CloudscapeAccountDAO();
}
public OrderDAO getOrderDAO() {
// CloudscapeOrderDAO implements OrderDAO
return new CloudscapeOrderDAO();
}
...
}

示例9.3中所示的CustomerDAO接口定义了Customer持久化对象的DAO方法,那些由所有具体DAO实现所实现的,如CloudscapeCustomerDAO、OracleCustomerDAO和SybaseCustomerDAO。类似的,但此处未列出的,AccountDAO和OrderDAO接口——分别为Account和Order业务对象定义DAO方法。

示例9.3Customer的Base DAO Interface

// 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(...);
...
}

CloudscapeCustomerDAO实现了CustomerDAO,参见示例9.4。其他DAO的实现,如CloudscapeAccountDAO、CloudscapeOrderDAO、OracleCustomerDAO和OracleAccountDAO等等,都是相似的。

示例9.4Customer的Cloudscape DAO实现

// CloudscapeCustomerDAO implementation of the 
// CustomerDAO interface. This class can contain all
// Cloudscape specific code and SQL statements.
// The client is thus shielded from knowing
// these implementation details.

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.
}
...
}

示例9.5所示的Customer Transfer Object。这是用来从客户端来发送和接收数据的DAO。Transfer Object的用法,在Transfer Object 模式中详细讨论。

示例9.5Customer Transfer Object

public class Customer implements java.io.Serializable {
// member variables
int CustomerNumber;
String name;
String streetAddress;
String city;
...

// getter and setter methods...
...
}

示例9.6展示了DAO工厂和DAO的用法。如果Cloudscape的实现变成了另一个产品,只需改变getDAOFactory()方法调用DAO工厂,以获得不同的工厂。

示例9.6使用DAO和DAO工厂 - 客户端代码

...
// 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.

...

相关模式

  • Transfer Object
    使用Transfer Object和客户端传输数据的DAO。

  • 工厂方法模式[GoF]和抽象工厂模式[GoF]
    数据访问对象策略的工厂使用工厂方法模式来实现具体的工厂及其产品(DAO)。为了额外的灵活性,可采用策略讨论中的抽象工厂模式。

  • 经纪人[POSA1]
    DAO模式涉及到经纪人模式(Broker pattern),它描述了分布式系统中客户端和服务器端的解耦方法。DAO模式,特别适用于这种模式,在另一层中把资源层从客户端中解耦出来,如业务层或表示层