??xml version="1.0" encoding="utf-8" standalone="yes"?> 在过?18 个月中,我参加了一个由有才华的软g工程师组成的组Q构建定制的、基?Web 的供应链理应用E序。我们的应用E序讉K范围q泛的持久性数据,包括配送状态、供应链衡量(metrics)、库存、货q发、项目管理数据和用户信息。我们用 JDBC API q接到我们公司的不同数据库^CQƈ在整个应用程序中使用 DAO 设计模式?/P>
?1 昄了应用程序和数据源之间的关系Q?/P>
在整个应用程序中使用数据讉K对象(DAO)使我们可以将底层数据讉K逻辑与业务逻辑分离开来。我们构Z为每一个数据源提供 GRUD (创徏、读取、更新、删?操作?DAO cR?/P>
在本文中Q我ؓ您介l构建更好的 DAO cȝ DAO 实现{略和技术。更切地说Q我讨论日志、异常处理和事务界定。您学到如何将q三者结合到自己?DAO cM。本文假定您熟悉 JDBC API、SQL 和关pL据库~程?/P>
我们以?DAO 设计模式和数据访问对象的概述开始?/P>
DAO 基础 具体?DAO cd含访问特定数据源的数据的逻辑。在下面一节中您将学习设计和实现数据访问对象的技术。有?DAO 设计模式的更多内容请参阅 参考资?/A>?/P>
事务界定 事务界定是定义事务边界的方式。J2EE 规范描述了两U事务界定的模型Q编E式(programmatic)和声明式(declarative)。表 1 分析了这两种模型Q?/P>
?1. 两种事务界定的模?/B>
我们侧重于~程式事务界定?/P>
设计考虑 了解上述问题的答案将有助于您选择最适合?DAO 的事务界定策略。在 DAO 中有两种主要的界定事务的{略。一U方式是?DAO 负责界定事务Q另一U将事务界定交给调用q个 DAO Ҏ的对象处理。如果选择了前一U方式,那么将事务代码嵌入?DAO 中。如果选择后一U方式,那么事务界定代码是?DAO cd面。我们将使用单的代码CZ帮助您更好理解每一U方式是如何工作的?/P>
清单 1 昄了一个有两种数据操作?DAOQ创建和更新Q?/P>清单 1. DAO Ҏ 清单 2 昄了一个简单的事务。事务界定在 DAO cd面。注意在q个例子中调用者是如何在一个事务中l合多个 DAO 操作的?/P>清单 2. 调用者管理的事务 q种事务界定{略对于需要在一个事务中讉K多个 DAO 的应用程序特别有用?/P>
可以?JDBC API 或?Java 事务 API(Java Transaction API JTA)实现事务界定?JDBC 事务界定?JTA 事务界定要简单,但是 JTA 提供了更多的灉|性。在下面一节中我将更深入地分析事务界定的机制?/P>
?JDBC q行事务界定 清单 3 昄了如何用 JDBC API 界定一个事务: 使用 JDBC 事务界定Ӟ您可以将多个 SQL 语句l合C个事务中。JDBC 事务的一个缺Ҏ事务的范围局限于一个数据库q接。一?JDBC 事务不能跨越多个数据库。在下面Q我们将看一下如何用 JTA q行事务界定。因?JTA 不像 JDBC 那样有名Q所以我们首先做一个简介?/P>
JTA ?/SPAN> JTA 事务?JDBC 事务功能更强。JDBC 事务局限ؓ一个数据库q接Q?JTA 事务可以有多个参与者。所有下?Java q_lg都可以参?JTA 事务Q?/P>
使用 JTA 的事务界?/SPAN> 当应用程序找C 当应用程序调?CODE> commit()Ӟ事务理器用一个两阶段的提交协议结束事务?/P>
控制事务?JTA Ҏ 应用E序调用 使用 JTA ?JDBC 您将需要用应用服务器的理工具讄 J2EE 应用E序?JNDI 查询数据源。一旦应用程序找C数据源对象,它就调用 XA q接与非 XA q接不同。一定要C XA q接参与?JTA 事务。这意味着 XA q接不支?JDBC 的自动提交功能。同Ӟ应用E序一定不要对 XA q接调用 选择最好的方式 在最q的许多目中,我们组是用 JDBC API q事务界定来构徏 DAO cȝ。这?DAO cd以ȝ如下Q?/P>
JDBC 事务q不L适合复杂的企业应用程序。如果您的事务要跨越多个 DAO 或者多个数据库Q那么下列实现策略也许更合适: JDBC 方式׃其简单性而具有吸引力QJTA 方式提供了更大的灉|性。您所选择的实现将取决于应用程序的特定需求?/P>
日志记录?DAO 在本节,我将展示一个显C如何将 Jakarta Commons Logging 加入?DAO 中的代码CZ。在q之前,让我们回一下一些基本知识?/P>
选择日志?/SPAN> ?2. Java q_的日志库
DAO 模式是标?J2EE 设计模式之一。开发h员用q种模式底层数据访问操作与高层业务逻辑分离开。一个典型的 DAO 实现有以下组Ӟ
关于 DAO 要记住的重要一Ҏ它们是事务性对象。由 DAO 所执行的每一个操?-- 如创建、更新或者删除数?-- 都与一个事务相兌。因此,事务界定的概念就变得特别重要了?/P>
声明式事务界?/B>
~程式事务界?/B>
E序员用 EJB 部v描述W声明事务属性?/TD>
E序员负责编写事务逻辑?/TD>
q行时环?EJB 容器)用这些属性自动管理事务?/TD>
应用E序通过一?API 控制事务?/TD>
如前所qͼDAO 是事务性对象。一个典型的 DAO 执行像创建、更新和删除q样的事务性操作。在设计 DAO Ӟ首先要问自己以下问题Q?/P>
public void createWarehouseProfile(WHProfile profile);
public void updateWarehouseStatus(WHIdentifier id, StatusInfo status);
tx.begin(); // start the transaction
dao.createWarehouseProfile(profile);
dao.updateWarehouseStatus(id1, status1);
dao.updateWarehouseStatus(id2, status2);
tx.commit(); // end the transaction
JDBC 事务是用 Connection
对象控制的。JDBC Connection 接口(java.sql.Connection
)提供了两U事务模式:自动提交和手工提交?CODE>java.sql.Connection 提供了以下控制事务的ҎQ?/P>
public void setAutoCommit(boolean)
public boolean getAutoCommit()
public void commit()
public void rollback()
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();
// ...
Java 事务 API(JTA) 及其同门兄弟 Java 事务服务(Java Transaction Service JTS)?J2EE q_提供了分布式事务服务。一?I xmlns:dw="http://www.ibm.com/developerWorks/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">分布式的事务涉及一个事务管理器和一个或者多个资源管理器。一?I xmlns:dw="http://www.ibm.com/developerWorks/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">资源理?/I>是Q何类型的持久性的数据存储。事务管理器负责协调所有事务参与者之间的通信。事务管理器与资源管理器之间的关pd?2 所C:
PersistenceManager
对象
要用 JTA q行事务界定Q应用程序要调用 javax.transaction.UserTransaction
接口中的Ҏ。清?4 昄了对 UserTransaction
对象的典?JNDI 查询Q?/P>清单 4. 一个对 UserTransaction 对象?/B> JDNI 查询
import javax.transaction.*;
import javax.naming.*;
// ...
InitialContext ctx = new InitialContext();
Object txObj = ctx.lookup("java:comp/UserTransaction");
UserTransaction utx = (UserTransaction) txObj;
UserTransaction
对象后,可以开始事务了Q如清单 5 所C:
utx.begin();
// ...
DataSource ds = obtainXADataSource();
Connection conn = ds.getConnection();
pstmt = conn.prepareStatement("UPDATE MOVIES ...");
pstmt.setString(1, "Spinal Tap");
pstmt.executeUpdate();
// ...
utx.commit();
// ...
javax.transaction.UserTransaction
接口提供了以下事务控制方法:
public void begin()
public void commit()
public void rollback()
public int getStatus()
public void setRollbackOnly()
public void setTransactionTimeout(int)
begin()
开始事务。应用程序调?commit()
或?CODE> rollback() l束事务。参?A xmlns:dw="http://www.ibm.com/developerWorks/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">参考资?/A>以了解更多关于用 JTA q行事务理的内宏V?/P>
开发h员通常?DAO cM?JDBC q行底层数据操作。如果计划用 JTA 界定事务Q那么就需要有一个实?javax.sql.XADataSource
?CODE>javax.sql.XAConnection ?javax.sql.XAResource
接口?JDBC 驱动E序。一个实Cq些接口的驱动程序将可以参与 JTA 事务。一?CODE> XADataSource 对象是一?XAConnection
对象的工厂?CODE>XAConnections 是参?JTA 事务?JDBC q接?/P>
XADataSource
。从应用服务器和 JDBC 驱动E序的文档中可以了解到相关的指导?/P>
javax.sql.DataSource.getConnection()
以获得到数据库的q接?/P>
java.sql.Connection.commit()
或?CODE> java.sql.Connection.rollback()。相反,应用E序应该使用 UserTransaction.begin()?/CODE>
?/P>
UserTransaction.commit()
?CODE> serTransaction.rollback()
我们讨论了如何用 JDBC ?JTA 界定事务。每一U方式都有其优点Q您需要决定哪一U最适合于您的应用程序?/P>
一个良好实现的 DAO cd使用日志记录来捕捉有兛_q行时行为的l节。您可以选择记录异常、配|信息、连接状态、JDBC 驱动E序元数据、或者查询参数。日志对于开发的所有阶D都很有用。我l常在开发时、测试时和生产中分析应用E序日志?/P>
许多开发h员用一U原始格式进行日志记录:System.out.println
?CODE> System.err.println?CODE>Println 语句速度快且使用方便Q但是它们没有提供全功能的日志记录系l所h的功能。表 2 列出?Java q_的日志库Q?/P>
日志?/B>
开放源代码Q?/B>
URL
java.util.logging
不是
http://java.sun.com/j2se/
Jakarta Log4j
?/TD>
http://jakarta.apache.org/log4j/
Jakarta Commons Logging
?/TD>
http://jakarta.apache.org/commons/logging.html
java.util.logging
?J2SE 1.4 q_上的标准 API。不q,大多数开发h员同?Jakarta Log4j 提供了更多的功能和更大的灉|性。Log4j 优于 java.util.logging 的一Ҏ它同时支?J2SE 1.3 ?J2SE 1.4 q_?/P>
Jakarta Commons Logging 可以?CODE> java.util.logging 或?Jakarta Log4j 一同用。Commons Logging 是一个日志抽象层Q它隔离了应用程序与底层日志实现。?Commons LoggingQ您可以通过改变配置文g更换底层日志实现。Commons Logging ?Jakarta Struts 1.1 ?Jakarta HttpClient 2.0 中用?/P>
一个日志记录示?/SPAN>
清单 7 昄了如何在 DAO cM使用 Jakarta Commons LoggingQ?/P>清单 7. DAO cM?Jakarta Commons Logging
|
日志记录是所有Q务关键型应用E序的重要部分。如果在 DAO 中遇到故障,那么日志通常可以提供判断出错位置的最好信息。将日志加入?DAO 可以保证您有Zq行调试和故障排除?/P>
DAO 中的异常处理
我们讨论q了事务界定和日志,现在对于如何在数据访问对象上应用它们有了更深入的理解。我们的W三个和最后一个讨题是异常处理。遵从几个简单的异常处理指导可以使您?DAO 更容易用、更健壮及更易于l护?/P>
在实?DAO 模式Ӟ考虑以下问题Q?/P>
在?DAO 模式的过E中Q我们的组开发了一些处理异常的原则。遵从这些原则可以极大地改进您的 DAOQ?/P>
java.lang.Exception
?CODE>java.lang.Exception 太一般化了。它不传递关于底层问题的M信息?BR>有关异常和异常处理技术的更多信息参阅参考资?/A>?/P>
实现实例Q?MovieDAO DAO 模式的这个实现包含下面列出的cd接口Q?/P>
?3. MovieDAO 实现
MovieDAO
是一个展C本文中讨论的所有技术的 DAOQ事务界定、日志和异常处理。您可以?A xmlns:dw="http://www.ibm.com/developerWorks/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">参考资?/A>一节中扑ֈ MovieDAO
源代码。代码分Z个包Q?/P>
daoexamples.exception
daoexamples.movie
daoexamples.moviedemo
daoexamples.movie.MovieDAOFactory
daoexamples.movie.MovieDAO
daoexamples.movie.MovieDAOImpl
daoexamples.movie.MovieDAOImplJTA
daoexamples.movie.Movie
daoexamples.movie.MovieImpl
daoexamples.movie.MovieNotFoundException
daoexamples.movie.MovieUtil
MovieDAO
接口定义?DAO 的数据操作。这个接口有五个ҎQ如下所C:
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)
daoexamples.movie
包包?MovieDAO
接口的两个实现。每一个实C用一U不同的方式q行事务界定Q如?3 所C:
MovieDAOImpl | MovieDAOImplJTA | |
实现 MovieDAO 接口? | ?/TD> | ?/TD> |
通过 JNDI 获得 DataSourceQ?/TD> | ?/TD> | ?/TD> |
?DataSource 获得 java.sql.Connection 对象Q?/TD> | ?/TD> | ?/TD> |
DAO 在内部界定事务? | ?/TD> | ?/TD> |
使用 JDBC 事务Q?/TD> | ?/TD> | ?/TD> |
使用一?XA DataSourceQ?/TD> | ?/TD> | ?/TD> |
参与 JTA 事务Q?/TD> | ?/TD> | ?/TD> |
MovieDAO 演示应用E序
q个演示应用E序是一个名?daoexamples.moviedemo.DemoServlet
?servlet cR?CODE>DemoServlet 使用q两?Movie DAO 查询和更新表中的电媄数据?/P>
q个 servlet 展示了如何将支持 JTA ?MovieDAO
?Java 消息服务(Java Message Service)l合C个事务中Q如清单 8 所C?/P>清单 8. ?MovieDAO ?JMS 代码l合C个事务中
|
要运行这个演C应用程序,需要在应用服务器上配置一?XA 数据源和一个非 XA 数据源。然后,部v daoexamples.ear 文g。这个应用程序可以在M兼容 J2EE 1.3 的应用服务器上运行。参?A xmlns:dw="http://www.ibm.com/developerWorks/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">参考资?/A>以获?EAR 文g和源代码?/P>
l束?/SPAN>
正如本文所展示的,实现 DAO 模式需要做比编写低U别的数据访问代码更多的工作。现在,通过选择一个适合您的应用E序的事务界定策略、通过?DAO cM加入日志记录Q以及通过遵从几项单的异常处理原则Q您可以构徏更好?DAO?/P>