文章和sun官方文档比较接近,给出英文文档地址:
http://java.sun.com/blueprints/corej2eepatterns/Patterns/DataAccessObject.html
J2EE开发人员使用数据访问对象(DAO)设计模式把底层的数据访问逻辑和高层的商务逻辑分开。实现DAO模式能够更加专注于编写数据访问代码。这篇文章中,Java开发人员Sean C. Sullivan从三个方面讨论DAO编程的结构特征:事务划分,异常处理,日志记录。
在最近的18个月,我和一个优秀的软件开发团队一起工作,开发定制基于WEB的供应链管理应用程序.我们的应用程序访问广泛的持久层数据,包括出货状态,供应链制度,库存,货物发运,项目管理数据,和用户属性等.我们使用JDBC API连接我们公司的各种数据库平台,并且在整个应用程序中应用了DAO设计模式.
通过在整个应用程序中应用数据访问对象(DAO)设计模式使我们能够把底层的数据访问逻辑和上层的商务逻辑分开.我们为每个数据源创建了提供CRUD(创建,读取,更新,删除)操作的DAO类.
在本文中,我将向你介绍DAO的实现策略以及创建更好的DAO类的技术.我会明确的介绍日志记录,异常处理,和事务划分三项技术.你将学在你的DAO类中怎样把这三种技术结合在一起.这篇文章假设你熟悉JDBC API,SQL和关系性数据库编程.
我们先来回顾一下DAO设计模式和数据访问对象.
DAO基础
DAO模式是标准的J2EE设计模式之一.开发人员使用这个模式把底层的数据访问操作和上层的商务逻辑分开.一个典型的DAO实现有下列几个组件:
1. 一个DAO工厂类;
2. 一个DAO接口;
3. 一个实现DAO接口的具体类;
4. 数据传递对象(有些时候叫做值对象).
具体的DAO类包含了从特定的数据源访问数据的逻辑。在下面的这段中你将学到设计和实现数据访问对象的技术。
事务划分:
关于DAO要记住的一件重要事情是它们是事务性对象。每个被DAO执行的操作(象创建,更新、或删除数据)都是和事务相关联的。同样的,事务划分(transaction demarcation)的概念是特别重要的。
事务划分是在事务界定定义中的方式。J2EE规范为事务划分描述了两种模式:编程性事务(programmatic)和声明性事务(declarative)。下表是对这两种模式的拆分:
声明性事务划分 编程性事务划分
程序员使用EJB的布署描述符声明事务属性 程序员担负编写事务逻辑代码的责任。
运行时环境(EJB容器)使用这些属性来自动的管理事务。 应用程序通过一个API接口来控制事务。
程序员使用EJB的布署描述符声明事务属性 程序员担负编写事务逻辑代码的责任。
运行时环境(EJB容器)使用这些属性来自动的管理事务。 应用程序通过一个API接口来控制事务。
我将把注意力集中的编程性事务划分上。
象前面的介绍一样,DAOs是一些事务对象。一个典型的DAO要执行象创建、更新、和删除这的事务性操作。在设计一个DAO时,首先要问自己如下问题:
1、 事务将怎样开始?
2、 事务将怎样结束?
3、 那个对象将承担起动一个事务的责任?
4、 那个对象将承担结束一个事务的责任?
5、 DAO应该承担起动和结束事务的责任?
6、 应用程序需要交叉访问多个DAO吗?
7、 一个事务包含一个DAO还是多个DAO?
8、 一个DAO包含其它的DAO中的方法吗?
回答这些问题将有助于你为DAO对象选择最好的事务划分策略。对ADO中的事务划分有两个主要的策略。一种方法是使用DAO承担事务划分的责任;另一种是延期性事务,它把事务划分到调用DAO对象的方法中。如果你选择前者,你将要在DAO类中嵌入事务代码。如果你选择后者,事务代码将被写在DAO类的外部。我们将使用简单的代码实例来更好的理解这两种方法是怎样工作的。
实例1展示了一个带有两种数据操作的DAO:创建(create)和更新(update):
public void createWarehouseProfile(WHProfile profile);
public void updateWarehouseStatus(WHIdentifier id, StatusInfo status);
public void updateWarehouseStatus(WHIdentifier id, StatusInfo status);
实例2展示了一个简单的事务,事务划分代码是在DAO类的外部。注意:在这个例子中的调用者把多个DOA操作组合到这个事务中。
tx.begin(); // start the transaction
dao.createWarehouseProfile(profile);
dao.updateWarehouseStatus(id1, status1);
dao.updateWarehouseStatus(id2, status2);
tx.commit(); // end the transaction
dao.createWarehouseProfile(profile);
dao.updateWarehouseStatus(id1, status1);
dao.updateWarehouseStatus(id2, status2);
tx.commit(); // end the transaction
这种事务事务划分策略对在一个单一事务中访问多个DAO的应用程序来说尤为重要。
你即可使用JDBC API也可以使用Java 事务API(JTA)来实现事务的划分。JDBC事务划分比JTA事务划分简单,但是JTA提供了更好的灵活性。在下面的这段中,我们会进一步的看事务划分机制。
使用JDBC的事务划分
JDBC事务是使用Connection对象来控制的。JDBC的连接接口(java.sql.Connection)提供了两种事务模式:自动提交和手动提交。Java.sql.Connection为控制事务提供了下列方法:
.public void setAutoCommit(Boolean)
.public Boolean getAutoCommit()
.public void commit()
.public void rollback()
.public Boolean getAutoCommit()
.public void commit()
.public void rollback()
实例3展示怎样使用JDBC API来划分事务:
import java.sql.*;
import javax.sql.*;
// …
DataSource ds = obtainDataSource();
Connection conn = ds.getConnection();
conn.setAutoCommit(false);
// …
pstmt = conn.prepareStatement(";Update MOVIES …";);
pstmt.setString(1, ";The Great Escape";);
pstmt.executeUpdate();
// …
conn.commit();
// …
import javax.sql.*;
// …
DataSource ds = obtainDataSource();
Connection conn = ds.getConnection();
conn.setAutoCommit(false);
// …
pstmt = conn.prepareStatement(";Update MOVIES …";);
pstmt.setString(1, ";The Great Escape";);
pstmt.executeUpdate();
// …
conn.commit();
// …
使用JDBC事务划分,你能够把多个SQL语句组合到一个单一事务中。JDBC事务的缺点之一就是事务范围被限定在一个单一的数据库连接中。一个JDBC事务不能够跨越多个数据库。接下来,我们会看到怎样使用JTA来做事务划分的。因为JTA不象JDBC那样被广泛的了解,所以我首先概要的介绍一下JTA。
JTA概要介绍
Java事务API(JTA;Java Transaction API)和它的同胞Java事务服务(JTS;Java Transaction Service),为J2EE平台提供了分布式事务服务。一个分布式事务(distributed transaction)包括一个事务管理器(transaction manager)和一个或多个资源管理器(resource manager)。一个资源管理器(resource manager)是任意类型的持久化数据存储。事务管理器(transaction manager)承担着所有事务参与单元者的相互通讯的责任。下车站显示了事务管理器和资源管理的间的关系。
JTA事务比JDBC事务更强大。一个JTA事务可以有多个参与者,而一个JDBC事务则被限定在一个单一的数据库连接。下列任一个Java平台的组件都可以参与到一个JTA事务中:
.JDBC连接
.JDO PersistenceManager 对象
.JMS 队列
.JMS 主题
.企业JavaBeans(EJB)
.一个用J2EE Connector Architecture 规范编译的资源分配器。
使用JTA的事务划分
要用JTA来划分一个事务,应用程序调用javax.transaction.UserTransaction接口中的方法。示例4显示了一个典型的JNDI搜索的UseTransaction对象。
import javax.transaction.*;
import javax.naming.*;
// …
InitialContext ctx = new InitialContext();
Object txObj = ctx.lookup(";java:comp/UserTransaction";);
UserTransaction utx = (UserTransaction) txObj;
import javax.naming.*;
// …
InitialContext ctx = new InitialContext();
Object txObj = ctx.lookup(";java:comp/UserTransaction";);
UserTransaction utx = (UserTransaction) txObj;
应用程序有了UserTransaction对象的引用之后,就可以象示例5那样来起动事务。
utx.begin();
// …
DataSource ds = obtainXADataSource();
Connection conn = ds.getConnection();
pstmt = conn.prepareStatement(";Update MOVIES …";);
pstmt.setString(1, ";Spinal Tap";);
pstmt.executeUpdate();
// …
utx.commit();
// …
// …
DataSource ds = obtainXADataSource();
Connection conn = ds.getConnection();
pstmt = conn.prepareStatement(";Update MOVIES …";);
pstmt.setString(1, ";Spinal Tap";);
pstmt.executeUpdate();
// …
utx.commit();
// …
当应用程序调用commit()时,事务管理器使用两段提交协议来结束事务。JTA事务控制的方法:
.javax.transaction.UserTransaction接口提供了下列事务控制方法:
.public void begin()
.public void commit()
.public void rollback()
.public void getStatus()
.public void setRollbackOnly()
.public void setTransactionTimeout(int)
.public void commit()
.public void rollback()
.public void getStatus()
.public void setRollbackOnly()
.public void setTransactionTimeout(int)
应用程序调用begin()来起动事务,即可调用commit()也可以调用rollback()来结束事务。
使用JTA和JDBC
开发人员经常使用JDBC来作为DAO类中的底层数据操作。如果计划使用JTA来划分事务,你将需要一个实现了javax.sql.XADataSource,javax.sql.XAConnection和javax.sql.XAResource接口JDBC的驱动。实现了这些接口的驱动将有能力参与到JTA事务中。一个XADataSource对象是一个XAConnection对象的工厂。XAConnections是参与到JTA事务中的连接。
你需要使用应用程序服务器管理工具来建立XADataSource对象。对于特殊的指令请参考应用程序服务器文档和JDBC驱动文档。
J2EE应用程序使用JNDI来查找数据源。一旦应用程序有了一个数据源对象的引用,这会调用javax.sql.DataSource.getConnection()来获得数据库的连接。
XA连接区别于非XA连接。要记住的是XA连接是一个JTA事务中的参与者。这就意味着XA连接不支持JDBC的自动提交特性。也就是说应用程序不必在XA连接上调用java.sql.Connection.commit()或java.sql.Connection.rollback()。相反,应用程序应该使用UserTransaction.begin()、UserTransaction.commit()和UserTransaction.rollback().
选择最好的方法
我们已经讨论了JDBC和JTA是怎样划分事务的。每一种方法都有它的优点,回此你需要决定为你的应用程序选择一个最适应的方法。 在我们团队许多最近的对于事务划分的项目中使用JDBC API来创建DAO类。这DAO类总结如下:
.事务划分代码被嵌入到DAO类内部
.DAO类使用JDBC API来进行事务划分
.调用者没有划分事务的方法
.事务范围被限定在一个单一的JDBC连接
JDBC事务对复杂的企业应用程序不总是有效的。如果你的事务将跨越多个DAO对象或多个数据库,那么下面的实现策略可能会更恰当:
.用JTA对事务进行划分
.事务划分代码被DAO分开
.调用者承担划分事务的责任
.DAO参与一个全局的事务中
JDBC方法由于它的简易性而具有吸引力,JTA方法提供了更多灵活性。你选择什么样的实现将依赖于你的应用程序的特定需求。
日志记录和DAO
一个好的DAO实现类将使用日志记录来捕获有关它在运行时的行为细节。你可以选择记录异常、配置信息、连接状态、JDBC驱动程序的元数据或查询参数。日志对开发整个阶段都是有益的。我经常检查应用程序在开发期间、测试期间和产品中的日志记录。
在这段中,我们将展现一段如何把Jakarta Commaons Logging结合中一个DAO中的例子。在我们开始之前,让我们先回顾一些基础知识。
选择一个日志例库
许多开发人员使用的基本日志形式是:System.out.println和System.err.println.Println语句。这种形式快捷方便,但它们不能提供一个完整的日志系统的的能力。下表列出了Java平台的日志类库:
日志类库 开源吗? URL
Java.util.logging 否 http://java.sun.com/j2ee
Jakarta Log4j 是 http://hajarta.apache.org/log4j/
Jakarta Commons Logging 是 http:/Jakarta.apache.org/commons/logging.html
Java.util.logging 否 http://java.sun.com/j2ee
Jakarta Log4j 是 http://hajarta.apache.org/log4j/
Jakarta Commons Logging 是 http:/Jakarta.apache.org/commons/logging.html
Java.util.logging是J2SE1.4平台上的标准的API。但是,大多数开发人员都认为Jakarta Log4j提供了更大的功能性和灵活性。Log4j超越java.util.logging的优点之一就是它支持J2SE1.3和J2SE1.4平台。
Jakarta Commons Logging能够被用于和java.util.loggin或Jakarta Log4j一起工作。Commons Logging是一个把你的应用程序独立于日志实现的提取层。使用Commons Logging你能够通过改变一个配置文件来与下面的日志实现来交换数据。Commons Logging被用于JAKARTA Struts1.1和Jakarta HttpClient2.0中。
一个日志示例
示例7显示了在一个DOA类中怎样使用Jakarta Commons Logging
import org.apache.commons.logging.*;
class DocumentDAOImpl implements DocumentDAO
{
static private final Log log = LogFactory.getLog(DocumentDAOImpl.class);
public void deleteDocument(String id)
{
// …
log.debug(";deleting document: "; + id);
// …
try
{
// … data operations …
}
catch (SomeException ex)
{
log.error(";Unable to delete document"; ex);
// … handle the exception …
}
}
}
class DocumentDAOImpl implements DocumentDAO
{
static private final Log log = LogFactory.getLog(DocumentDAOImpl.class);
public void deleteDocument(String id)
{
// …
log.debug(";deleting document: "; + id);
// …
try
{
// … data operations …
}
catch (SomeException ex)
{
log.error(";Unable to delete document"; ex);
// … handle the exception …
}
}
}
日志是评估应用程序的基本部分。如果你在一个DAO中遇到了失败,日志经常会为理解发生的什么错误提供最好的信息。把日志结合到你的DAO中,确保得到调试和解决问题的有效手段。
DAO中的异常处理
我们已经看了事务划分和日志记录,并且现在对于它们是怎样应用于数据访问对象的有一个深入的理解。我们第三部分也是最后要讨论的是异常处理。下面的一些简单的异常处理方针使用你的DAO更容易使用,更加健壮和更具有可维护性。
在实现DAO模式的时候,要考滤下面的问题:
.在DAO的public接口中的方法将抛出被检查的异常吗?
.如果是,将抛出什么样的检查性异常?
.在DAO实现类中怎能样处理异常。
在用DAO模式工作的过程中,我们的团队为异常处理开发了一组方针。下面的这些方针会很大程度的改善你的DAO:
.DAO方法应该抛出有意义的异常。
.DAO方法不应该抛出java.lang.Exception异常。因为java.lang.Exception太一般化,它不能包含有关潜在问题的所有信息。
.DAO方法不应该抛出java.sql.SQLException异常。SQLException是一个底层的JDBC异常,DAO应用努力封装JDBC异常而不应该把JDBC异常留给应用程序的其它部分。
.在DAO接口中的方法应该只抛出调用者期望处理的检查性异常。如果调用者不能用适当的方法来处理异常,考滤抛出不检查性(运行时run-time)异常。
.如果你的数据访问代码捕获了一个异常,不可要忽略它。忽略捕获异常的DAO是很处理的。
.使用异常链把底层的异常传递给高层的某个处理器。
.考滤定义一个标准的DAO异常类。Spring框架提供了一个优秀的预定义的DAO异常类的集合。
看Resources,查看有异常和异常处理技术的更详细信息。
实现示例:MovieDAO
MoveDAO是一个示范了在这篇文章中所讨论的所有技术,包括事务划分、日志记录和异常处理。你会在Resources段找到MovieDAO的源代码。它被分下面的三个包:
.daoexamples.exception
.daoexamples.move
.daoexamples.moviedemo
.daoexamples.move
.daoexamples.moviedemo
这个DAO模式的实现由下面的类和接口组成:
.daoexamples.movie.MovieDAOFactory
.daoexamples.movie.MovieDAO
.daoexamples.movie.MovieDAOImpl
.daoexamples.movie.MovieDAOImplJTA
.daoexamples.movie.Movie
.daoexamples.movie.MovieImple
.daoexamples.movie.MovieNotFoundException
.daoexamples.movie.MovieUtil
.daoexamples.movie.MovieDAO
.daoexamples.movie.MovieDAOImpl
.daoexamples.movie.MovieDAOImplJTA
.daoexamples.movie.Movie
.daoexamples.movie.MovieImple
.daoexamples.movie.MovieNotFoundException
.daoexamples.movie.MovieUtil
MovieDAO接口定义了DAO的数据操作。这个接口有如下五个方法:
.public Movie findMovieById(String id)
.public java.util.Collection findMoviesByYear(String year)
.public void deleteMovie(String id)
.public Movie createMovie(String rating,String year,String title)
.public void updateMovie(String id,String rating,String year,String title)
.public java.util.Collection findMoviesByYear(String year)
.public void deleteMovie(String id)
.public Movie createMovie(String rating,String year,String title)
.public void updateMovie(String id,String rating,String year,String title)
daoexamples.movie包包含了两个MovieDAO接口的实现。每个实现使用了一个同的事务划分方法,如下表所示:
MovieDAOImpl MovieDAOImplJTA
实现了MovieDAO接口吗? Yes Yes
通过JNDI获得DataSource吗? Yes Yes
从一个DataSource获得java.sql.Connection对象吗? Yes Yes
DAO界定内部的事务吗? Yes No
使用JDBC事务吗? Yes No
使用一个XA DataSource吗? No Yes
分担JTA事务吗? No Yes
实现了MovieDAO接口吗? Yes Yes
通过JNDI获得DataSource吗? Yes Yes
从一个DataSource获得java.sql.Connection对象吗? Yes Yes
DAO界定内部的事务吗? Yes No
使用JDBC事务吗? Yes No
使用一个XA DataSource吗? No Yes
分担JTA事务吗? No Yes
MovieDAO 示范应用程序
这个示范应用程序是一个叫做daoexamples.moviedemo.DemoServlet.DemoServlet的servlet类,它使用Movie DAO来查询和更新一个表中的movie数据。
这个servlet示范了把JTA感知的MovieDAO和Java消息服务组合到一个单一的事务中,如示例8所示:
UserTransaction utx = MovieUtil.getUserTransaction();
utx.begin();
batman = dao.createMovie(";R";
";2008";
";Batman Reloaded";);
publisher = new MessagePublisher();
publisher.publishTextMessage(";I’ll be back";);
dao.updateMovie(topgun.getId(),
";PG-13";
topgun.getReleaseYear(),
topgun.getTitle());
dao.deleteMovie(legallyblonde.getId());
utx.commit();
utx.begin();
batman = dao.createMovie(";R";
";2008";
";Batman Reloaded";);
publisher = new MessagePublisher();
publisher.publishTextMessage(";I’ll be back";);
dao.updateMovie(topgun.getId(),
";PG-13";
topgun.getReleaseYear(),
topgun.getTitle());
dao.deleteMovie(legallyblonde.getId());
utx.commit();
要运行这个范例应用程序,在你的应用程序服务器中配置一个XA 数据源和一个非XA数据源。然后布署daoexamples.ear文件。这个应用程序将运行在任何与J2EE兼容的应用程序服务器。
=====================================================
很多的J2EE应用程序需要使用持久性数据(数据库、文件等)。不同的程序,持久性存储是各不相同的,并且用来访问这些不同的持久性存储机制的API也有很大的不同。如果应用程序要在不同的持久性存储间迁移,这些访问特定持久存储层的代码将面临重写。
如何解决这个问题?且看"DAO模式"
数据访问对象(Data Acess Object) 模式
一.环境
根据数据源不同,数据访问也不同。根据存储的类型(关系数据库、面向对象数据库、文件等等)和供应商实现不同,持久性存储(比如数据库)的访问差别也很大。
二.问题
许多真是的J2EE应用程序需要在一定程度上使用持久性数据。对于许多应用程序,持久性存储是使用不同的机制实现的,并且用来访问这些不同的持久性存储机制的API也有很大的不同。
比如,应用程序使用实体bean(这里应该是指BMP的bean,CMP的bean已大大降低了与RDBMS的耦合)的分布式组件来表示持久性数据,或者使用JDBC API来访问驻留在某关系数据库管理系统(RDBMS)中的数据,这些组件中包含连接性性和数据访问代码会引入这些组件与数据源实现之间的紧密耦合。组件中这类代码依赖性使应用程序从某种数据源迁移到其他种类的数据源将变得非常麻烦和困难。当数据源变化时,组件也需要改变,以便于能够处理新类型的数据源。
(举个例子来说,我们UPTEL系统是使用JDBC API对 orACLE数据库进行连接和数据访问的,这些JDBC API与SQL语句散布在系统中,当我们需要将UPTEL迁移到其他RDBMS时,比如曾经迁移到INFORMIX,就面临重写数据库连接和访问数据的模块。)
三.作用力
1.诸如bean管理的实体bean、会话bean、servlet等组件往往需要从持久性存储数据源中检索数据,以及进行数据存储等操作。
2.根据产品供应商的不同,持久性存储API差别也很大,这些API和其能力同样根据存储的类型不同也有差别,这样存在以下缺点,即访问这些独立系统的API很不统一。
3.组件需要透明于实际的持久性存储或者数据源实现,以便于提供到不同供应商产品、不同存储类型和不同数据源类型的更容易的移植性。
四.解决方案
使用数据访问对象(DAO)模式来抽象和封装所有对数据源的访问。DAO管理着与数据源的连接以便检索和存储数据。
DAO实现了用来操作数据源的访问机制。数据源可以时RDBMS,LDAP,File等。依赖于DAO的业务组件为其客户端使用DAO提供更简单的接口。DAO完全向客户端隐藏了数据源实现细节。由于当低层数据源实现变化时,DAO向客户端提供的接口不会变化,所有该模式允许DAO调整到不同的存储模式,而不会影响其客户端或者业务组件。重要的是,DAO充当组件和数据源之间的适配器。
(按照这个理论,如果我们UPTEL系统使用了DAO模式,就可以无缝的从ORACLE迁移到任何一个RDBMS了。梦想总是很完美的,且看看DAO模式如何实现)
1.结构,图1是表示DAO模式中各种关系的类图。
此主题相关图片如下:
2.参与者和职责
1)BusinessObject(业务对象)
代表数据客户端。正是该对象需要访问数据源以获取和存储数据。
2)DataAccessObject(数据访问对象)
是该模式的主要对象。DataAccessObject抽取该BusinessObject的低层数据访问实现,以保证对数据源的透明访问。BusinessObject也可以把数据加载和存储操作委托给DataAccessObject。
3)DataSource(数据源)
代表数据源实现。数据源可以是各RDBMSR数据库,OODBMS,XML文件等等。
4)valueObject(值对象)
代表用做数据携带着的值对象。DataAccessObject可以使用值对象来把数据返回给客户端。
DataAccessObject也许会接受来自于客户端的数据,其中这些用于更新数据源的数据存放于值对象中来传递。
3.策略
1).自动DAO代码产生策略
因为每个BusinessObject对应于一个特殊的DAO,因此有可能建立BusinessObject,DAO和低层实现(比如RDBMS中的表)之间的关系(映射)。一点这些关系(映射)已经建立,我们就可以编写与应用程序有馆的代码生成的简单工具了(什么?自己写GP程序?用ORM的附带工具自动生成不就完了,最多自己写几个Adapter,牛人就是不同,啥都要自己写…),其中的工具可以产生该应用程序需要的所有DAO代码。
如果DAO需求很复杂,我们可以采用第三方工具,其中这些工具提供对象到RDBMS数据库的关系映射(这里指的是前面提到的ORM工具,全称是Object Relation Mapping,目前成熟的ORM工具有很多:Hibernate,OJB,Torque,TopLink等等)。
这些工具通常包含GUI工具来把业务对象映射到持久性存储对象,并且因而定义中间DAO。一旦这些映射完成,这些工具会自动地生成代码,并且也许会提供其他增值功能,比如结果缓冲、查询缓冲、与应用程序集成,以及与其他第三方产品(比如分布式缓冲)地继承,等等。
(增值服务:Torque提供了结果缓冲,Hibernate提供了对Oracle数据库SQL指令的优化,OJB提供JDO API、OMDB API)
2).数据访问对象的工厂策略
通过调整抽象工厂和工厂方法模式,DAO模式可以达到很高的灵活度。
当低层存储不会随着实现变化而变化时,该策略可以使用工厂方法模式来实现该策略。以产生应用程序需要的大量DAO。图2是这种情况下的类图。
此主题相关图片如下:
当低层存储随着实现变化而变化时,该策略可以使用抽象工厂方法模式而实现。
图3是这种情况下的类图。
此主题相关图片如下:
5.结果
1).启用透明性
业务对象可以是使用数据源,而无须了解该数据源实现的具体细节。访问是透明的,原因是实现被隐藏在DAO的内部。
2).启用更容易的迁移
DAO层使应用程序更加容易地迁移到一个不同的数据库实现。业务对象不了解低层数据实现。因而,该迁移只涉及对DAO层的变化。更进一步说,如果使用工厂策略,则有可能为每一个低层存储实现提供一个具体工厂实现。在这种情况下,迁移到不同的迁移实现意味着给应用程序提供一个新的工厂实现。
3).减少业务对象中代码复杂度
由于DAO管理所有的数据访问复杂性,它可以简化业务对象和其他使用DAO的客户端中的代码。所有与实现有关的代码(比如sql语句)都被包含在DAO中,而不是包含在业务对象中。这样做提高了代码的可读性,已经代码生产效率。
4).把所有的数据访问集中到一个独立的层。
因为所有的数据访问操作现在被委托给DAO,所有单独的数据访问层可以被看作把数据访问实现与应用程序中的其他代码相隔离的。这种集中化使应用程序更容易地维护和管理。
5).不适用于容器管理的持久性
由于EJB容器用容器管理的持久性(CMP)来管理实体bean,该容器会自动地服务所有的持久性存储访问。使用容器管理的实体bean的应用程序不需要DAO层,因为该应用程序服务器透明地提供该功能。然而,当需要组合使用CMP和BMP时,DAO仍旧有用处。
6).添加其他层
DAO会在数据客户端和数据源之间创建其他的对象层,其中该数据源需要被设计和实现以便于权衡该模式的好处。但是选择本方法也会带来额外的开销。
7).需要类层次设计
在使用工厂策略时,我们需要设计和实现具体工厂的层次,以及这些工厂产生的具体产品层次。如果能够确保这种灵活性,则有必要考虑这种额外的工作。这样做会增加设计的复杂性。然而,在实现该工厂策略时,你可以首先考虑工厂方法模式,然后再根据需要过渡到抽象工厂。
六.范例代码
1.实现数据访问对象模式
范例9-4时表示Customer信息的持久性对象的DAO范例代码。当findCustomer()被调用时,CloudscapeCustomerDAO创建一个Customer值对象。
范例9-6是使用DAO的范例代码。
2.实现数据访问对象的工厂策略
1)使用工厂方法模式
2)使用抽象工厂模式
范例代码9-2是CloudscapeDAOFactory的范例代码。
范例代码9-3中的CustomerDAO接口为Customer持久性对象定义了DAO方法,这些接口是被所有具体DAO实现来实现的,比如CloudscapeCustomerDAO、OracleCustomerDAO、已经SybaseCustomerDAO。Account和OrederDAO接口也与此类似。
Example 9.1 Abstract DAOFactory Class
// 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 :
很多的J2EE应用程序需要使用持久性数据(数据库、文件等)。不同的程序,持久性存储是各不相同的,并且用来访问这些不同的持久性存储机制的API也有很大的不同。如果应用程序要在不同的持久性存储间迁移,这些访问特定持久存储层的代码将面临重写。
如何解决这个问题?且看"DAO模式"
数据访问对象(Data Acess Object) 模式
一.环境
根据数据源不同,数据访问也不同。根据存储的类型(关系数据库、面向对象数据库、文件等等)和供应商实现不同,持久性存储(比如数据库)的访问差别也很大。
二.问题
许多真是的J2EE应用程序需要在一定程度上使用持久性数据。对于许多应用程序,持久性存储是使用不同的机制实现的,并且用来访问这些不同的持久性存储机制的API也有很大的不同。
比如,应用程序使用实体bean(这里应该是指BMP的bean,CMP的bean已大大降低了与RDBMS的耦合)的分布式组件来表示持久性数据,或者使用JDBC API来访问驻留在某关系数据库管理系统(RDBMS)中的数据,这些组件中包含连接性性和数据访问代码会引入这些组件与数据源实现之间的紧密耦合。组件中这类代码依赖性使应用程序从某种数据源迁移到其他种类的数据源将变得非常麻烦和困难。当数据源变化时,组件也需要改变,以便于能够处理新类型的数据源。
(举个例子来说,我们UPTEL系统是使用JDBC API对 orACLE数据库进行连接和数据访问的,这些JDBC API与SQL语句散布在系统中,当我们需要将UPTEL迁移到其他RDBMS时,比如曾经迁移到INFORMIX,就面临重写数据库连接和访问数据的模块。)
三.作用力
1.诸如bean管理的实体bean、会话bean、servlet等组件往往需要从持久性存储数据源中检索数据,以及进行数据存储等操作。
2.根据产品供应商的不同,持久性存储API差别也很大,这些API和其能力同样根据存储的类型不同也有差别,这样存在以下缺点,即访问这些独立系统的API很不统一。
3.组件需要透明于实际的持久性存储或者数据源实现,以便于提供到不同供应商产品、不同存储类型和不同数据源类型的更容易的移植性。
四.解决方案
使用数据访问对象(DAO)模式来抽象和封装所有对数据源的访问。DAO管理着与数据源的连接以便检索和存储数据。
DAO实现了用来操作数据源的访问机制。数据源可以时RDBMS,LDAP,File等。依赖于DAO的业务组件为其客户端使用DAO提供更简单的接口。DAO完全向客户端隐藏了数据源实现细节。由于当低层数据源实现变化时,DAO向客户端提供的接口不会变化,所有该模式允许DAO调整到不同的存储模式,而不会影响其客户端或者业务组件。重要的是,DAO充当组件和数据源之间的适配器。
(按照这个理论,如果我们UPTEL系统使用了DAO模式,就可以无缝的从ORACLE迁移到任何一个RDBMS了。梦想总是很完美的,且看看DAO模式如何实现)
1.结构,图1是表示DAO模式中各种关系的类图。
此主题相关图片如下:
2.参与者和职责
1)BusinessObject(业务对象)
代表数据客户端。正是该对象需要访问数据源以获取和存储数据。
2)DataAccessObject(数据访问对象)
是该模式的主要对象。DataAccessObject抽取该BusinessObject的低层数据访问实现,以保证对数据源的透明访问。BusinessObject也可以把数据加载和存储操作委托给DataAccessObject。
3)DataSource(数据源)
代表数据源实现。数据源可以是各RDBMSR数据库,OODBMS,XML文件等等。
4)valueObject(值对象)
代表用做数据携带着的值对象。DataAccessObject可以使用值对象来把数据返回给客户端。
DataAccessObject也许会接受来自于客户端的数据,其中这些用于更新数据源的数据存放于值对象中来传递。
3.策略
1).自动DAO代码产生策略
因为每个BusinessObject对应于一个特殊的DAO,因此有可能建立BusinessObject,DAO和低层实现(比如RDBMS中的表)之间的关系(映射)。一点这些关系(映射)已经建立,我们就可以编写与应用程序有馆的代码生成的简单工具了(什么?自己写GP程序?用ORM的附带工具自动生成不就完了,最多自己写几个Adapter,牛人就是不同,啥都要自己写…),其中的工具可以产生该应用程序需要的所有DAO代码。
如果DAO需求很复杂,我们可以采用第三方工具,其中这些工具提供对象到RDBMS数据库的关系映射(这里指的是前面提到的ORM工具,全称是Object Relation Mapping,目前成熟的ORM工具有很多:Hibernate,OJB,Torque,TopLink等等)。
这些工具通常包含GUI工具来把业务对象映射到持久性存储对象,并且因而定义中间DAO。一旦这些映射完成,这些工具会自动地生成代码,并且也许会提供其他增值功能,比如结果缓冲、查询缓冲、与应用程序集成,以及与其他第三方产品(比如分布式缓冲)地继承,等等。
(增值服务:Torque提供了结果缓冲,Hibernate提供了对Oracle数据库SQL指令的优化,OJB提供JDO API、OMDB API)
2).数据访问对象的工厂策略
通过调整抽象工厂和工厂方法模式,DAO模式可以达到很高的灵活度。
当低层存储不会随着实现变化而变化时,该策略可以使用工厂方法模式来实现该策略。以产生应用程序需要的大量DAO。图2是这种情况下的类图。
此主题相关图片如下:
当低层存储随着实现变化而变化时,该策略可以使用抽象工厂方法模式而实现。
图3是这种情况下的类图。
此主题相关图片如下:
5.结果
1).启用透明性
业务对象可以是使用数据源,而无须了解该数据源实现的具体细节。访问是透明的,原因是实现被隐藏在DAO的内部。
2).启用更容易的迁移
DAO层使应用程序更加容易地迁移到一个不同的数据库实现。业务对象不了解低层数据实现。因而,该迁移只涉及对DAO层的变化。更进一步说,如果使用工厂策略,则有可能为每一个低层存储实现提供一个具体工厂实现。在这种情况下,迁移到不同的迁移实现意味着给应用程序提供一个新的工厂实现。
3).减少业务对象中代码复杂度
由于DAO管理所有的数据访问复杂性,它可以简化业务对象和其他使用DAO的客户端中的代码。所有与实现有关的代码(比如sql语句)都被包含在DAO中,而不是包含在业务对象中。这样做提高了代码的可读性,已经代码生产效率。
4).把所有的数据访问集中到一个独立的层。
因为所有的数据访问操作现在被委托给DAO,所有单独的数据访问层可以被看作把数据访问实现与应用程序中的其他代码相隔离的。这种集中化使应用程序更容易地维护和管理。
5).不适用于容器管理的持久性
由于EJB容器用容器管理的持久性(CMP)来管理实体bean,该容器会自动地服务所有的持久性存储访问。使用容器管理的实体bean的应用程序不需要DAO层,因为该应用程序服务器透明地提供该功能。然而,当需要组合使用CMP和BMP时,DAO仍旧有用处。
6).添加其他层
DAO会在数据客户端和数据源之间创建其他的对象层,其中该数据源需要被设计和实现以便于权衡该模式的好处。但是选择本方法也会带来额外的开销。
7).需要类层次设计
在使用工厂策略时,我们需要设计和实现具体工厂的层次,以及这些工厂产生的具体产品层次。如果能够确保这种灵活性,则有必要考虑这种额外的工作。这样做会增加设计的复杂性。然而,在实现该工厂策略时,你可以首先考虑工厂方法模式,然后再根据需要过渡到抽象工厂。
六.范例代码
1.实现数据访问对象模式
范例9-4时表示Customer信息的持久性对象的DAO范例代码。当findCustomer()被调用时,CloudscapeCustomerDAO创建一个Customer值对象。
范例9-6是使用DAO的范例代码。
2.实现数据访问对象的工厂策略
1)使用工厂方法模式
2)使用抽象工厂模式
范例代码9-2是CloudscapeDAOFactory的范例代码。
范例代码9-3中的CustomerDAO接口为Customer持久性对象定义了DAO方法,这些接口是被所有具体DAO实现来实现的,比如CloudscapeCustomerDAO、OracleCustomerDAO、已经SybaseCustomerDAO。Account和OrederDAO接口也与此类似。
Example 9.1 Abstract DAOFactory Class
// 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 :
沒有留言:
張貼留言