在过去的18个月中,我与一群才华横溢的软件工程师合作,构建了基于Web的定制自定义供应链管理应用程序。 我们的应用程序访问了广泛的持久性数据,包括装运状态,供应链指标,仓库库存,承运人发票,项目管理数据和用户资料。 我们使用JDBC API连接到公司的各种数据库平台,并在整个应用程序中应用了DAO设计模式。
图1显示了应用程序和数据源之间的关系:
图1.应用程序和数据源
在整个应用程序中应用数据访问对象(DAO)模式使我们能够将低级数据访问逻辑与业务逻辑分开。 我们构建了DAO类,为每个数据源提供CRUD(创建,读取,更新,删除)操作。
在本文中,我将向您介绍用于构建更好的DAO类的DAO实施策略和技术。 具体来说,我将介绍日志记录,异常处理和事务划分。 您将学习如何在您的DAO类中结合这三个方面。 本文假定您熟悉JDBC API,SQL和关系数据库编程。
我们将首先回顾DAO设计模式和数据访问对象。
DAO基础
DAO模式是标准的J2EE设计模式之一。 开发人员使用此模式将底层数据访问操作与高层业务逻辑分开。 典型的DAO实现具有以下组件:
- DAO工厂类
- DAO接口
- 实现DAO接口的具体类
- 数据传输对象(有时称为值对象)
具体的DAO类包含用于从特定数据源访问数据的逻辑。 在以下各节中,您将学习设计和实现数据访问对象的技术。 请参阅相关主题 ,以了解更多关于DAO设计模式。
交易划分
关于DAO,要记住的重要一点是它们是事务对象。 DAO执行的每个操作(例如创建,更新或删除数据)都与事务相关联。 因此, 事务划分的概念非常重要。
事务划分是定义事务边界的方式。 J2EE规范描述了两个用于事务划分的模型:编程模型和声明性模型。 表1细分了两个模型:
表1.事务划分的两种模型
声明式事务划分 | 程序化交易划分 |
---|---|
程序员使用EJB部署描述符声明事务属性。 | 程序员负责编码事务逻辑。 |
运行时环境(EJB容器)使用属性来自动管理事务。 | 该应用程序通过API控制交易。 |
我们将重点关注程序化事务划分。
设计注意事项
如前所述,DAO是事务对象。 典型的DAO执行事务操作,例如创建,更新和删除。 设计DAO时,首先要问自己以下问题:
- 交易将如何开始?
- 交易将如何结束?
- 哪个对象将负责开始交易?
- 哪个对象负责结束交易?
- DAO是否应负责开始和结束交易?
- 应用程序是否需要跨多个DAO访问数据?
- 一笔交易会涉及一个DAO还是多个DAO?
- DAO会在另一个DAO上调用方法吗?
了解这些问题的答案将帮助您选择最适合您的DAO的事务划分策略。 DAO中有两种主要的事务划分策略。 一种方法使DAO负责划分事务。 另一个将事务划分推迟到调用DAO方法的对象。 如果选择前一种方法,则将事务代码嵌入DAO类中。 如果选择后一种方法,则事务划分代码将在DAO类的外部。 我们将使用简单的代码示例来更好地理解每种方法的工作原理。
清单1显示了具有两个数据操作的DAO:创建和更新:
清单1. DAO方法
public void createWarehouseProfile(WHProfile profile);
public void updateWarehouseStatus(WHIdentifier id,
StatusInfo status);
清单2显示了一个简单的事务。 事务划分代码在DAO类的外部。 请注意,此示例中的调用方如何在事务中合并多个DAO操作。
清单2.呼叫者管理的事务
tx.begin(); // start 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 Connection接口( java.sql.Connection
)提供两种事务处理模式:自动提交和手动提交。 java.sql.Connection
提供以下用于控制事务的方法:
-
public void setAutoCommit(boolean)
-
public boolean getAutoCommit()
-
public void commit()
-
public void rollback()
清单3显示了如何使用JDBC API划分事务:
清单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();
// ...
使用JDBC事务划分,您可以将多个SQL语句组合到一个事务中。 JDBC事务的缺点之一是,事务的范围仅限于单个数据库连接。 JDBC事务不能跨越多个数据库。 接下来,我们将看到如何使用JTA完成事务划分。 由于JTA不像JDBC那样广为人知,因此我们将从概述开始。
JTA概述
Java事务API(JTA)及其兄弟Java事务服务(JTS)为J2EE平台提供分布式事务服务。 分布式事务涉及一个事务管理器和一个或多个资源管理器。 资源管理器是任何类型的持久性数据存储。 交易经理负责协调所有交易参与者之间的通信。 事务管理器和资源管理器之间的关系如图2所示:
图2.事务管理器和资源管理器
JTA事务比JDBC事务更强大。 虽然JDBC事务仅限于单个数据库连接,但JTA事务可以有多个参与者。 以下Java平台组件中的任何一个都可以参与JTA事务:
- JDBC连接
- JDO
PersistenceManager
对象 - JMS队列
- JMS主题
- 企业JavaBeans
- 符合J2EE连接器体系结构规范的资源适配器
与JTA进行交易划分
为了使用JTA划分事务,应用程序在javax.transaction.UserTransaction
接口上调用方法。 清单4显示了UserTransaction
对象的典型JNDI查找:
清单4. UserTransaction对象的JNDI查找
import javax.transaction.*;
import javax.naming.*;
// ...
InitialContext ctx = new InitialContext();
Object txObj =
ctx.lookup("java:comp/UserTransaction");
UserTransaction utx = (UserTransaction) txObj;
在应用程序对UserTransaction
对象进行引用之后,它可以启动事务,如清单5所示:
清单5.使用JTA启动事务
utx.begin();
// ...
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 int getStatus()
-
public void setRollbackOnly()
-
public void setTransactionTimeout(int)
要启动事务,应用程序调用begin()
。 为了结束事务,应用程序调用commit()
或rollback()
。 请参阅相关主题 ,以了解更多有关使用JTA事务管理。
使用JTA和JDBC
开发人员经常将JDBC用于DAO类中的低级数据操作。 如果计划使用JTA划分事务,则需要一个JDBC驱动程序,该驱动程序实现javax.sql.XADataSource
, javax.sql.XAConnection
和javax.sql.XAResource
接口。 实现这些接口的驱动程序将能够参与JTA事务。 XADataSource
对象是XAConnection
对象的工厂。 XAConnection
是参与JTA事务的JDBC连接。
您将需要使用应用程序服务器的管理工具来设置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 Commons Logging集成到DAO中。 在此之前,让我们回顾一些基础知识。
选择日志库
许多开发人员使用原始形式的日志记录: System.out.println
和System.err.println
。 Println
语句快速便捷,但它们不具备功能齐全的日志记录系统的功能。 表2列出了Java平台的日志记录库:
表2. Java平台的日志记录库
记录库 | 开源? | 网址 |
---|---|---|
java.util.logging | 没有 | http://java.sun.com/j2se/ |
雅加达Log4j | 是 | http://jakarta.apache.org/log4j/ |
雅加达公共伐木 | 是 | http://jakarta.apache.org/commons/logging.html |
java.util.logging
是J2SE 1.4平台的标准API。 但是,大多数开发人员都会同意,Jakarta Log4j提供了更大的功能和更大的灵活性。 Log4j相对于java.util.logging的优点之一是它同时支持J2SE 1.3和J2SE 1.4平台。
Jakarta Commons Logging可以与java.util.logging
或Jakarta Log4j结合使用。 Commons Logging是一个日志记录抽象层,它将您的应用程序与基础日志记录实现隔离。 使用Commons Logging,您可以通过更改配置文件来交换基础的日志记录实现。 Jakarta Struts 1.1和Jakarta HttpClient 2.0中使用了Commons Logging。
记录示例
清单7显示了如何在DAO类中使用Jakarta Commons Logging:
清单7.登录DAO类的Jakarta Commons
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 ...
}
}
}
日志记录是任何关键任务应用程序的重要组成部分。 如果您在DAO中遇到故障,日志通常会提供最佳信息,以帮助您了解发生了什么问题。 将日志记录合并到DAO中可确保您具备调试和故障排除的能力。
DAO中的异常处理
我们已经研究了事务划分和日志记录,现在您对每个事务如何应用于数据访问对象有了更深入的了解。 我们的第三个也是最后一个讨论点是异常处理。 遵循一些简单的异常处理准则将使您的DAO更加易于使用,更强大和更可维护。
实施DAO模式时,请考虑以下问题:
- DAO的公共接口中的方法会抛出检查异常吗?
- 如果是,将抛出哪些检查异常?
- 在DAO实现类中如何处理异常?
在使用DAO模式的过程中,我们的团队为异常处理制定了一套准则。 请遵循以下准则,以大大改善您的DAO:
- DAO方法应引发有意义的异常。
- DAO方法不应抛出
java.lang.Exception
。java.lang.Exception
太通用了。 它不传达有关潜在问题的任何信息。 - DAO方法不应抛出
java.sql.SQLException
。 SQLException是低级JDBC异常。 DAO应该努力封装JDBC,而不是将JDBC暴露给应用程序的其余部分。 - 仅在可以合理预期调用者处理该异常的情况下,DAO接口中的方法才应引发检查的异常。 如果调用者无法以有意义的方式处理该异常,请考虑引发一个未经检查的(运行时)异常。
- 如果您的数据访问代码捕获到异常,请不要忽略它。 忽略捕获的异常的DAO很难进行故障排除。
- 使用链接的异常将低级异常转换为高级异常。
- 考虑定义标准的DAO异常类。 Spring框架(请参阅参考资料 )提供了一组出色的预定义DAO异常类。
请参阅相关主题有关异常和异常处理技术的更多详细信息。
实施示例:MovieDAO
MovieDAO
是DAO,它演示了本文讨论的所有技术:事务划分,日志记录和异常处理。 您可以在“ 相关主题”部分中找到MovieDAO
源。 该代码分为三个包:
-
daoexamples.exception
-
daoexamples.movie
-
daoexamples.moviedemo
DAO模式的此实现包括如下所示的类和接口:
-
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的数据操作。 该接口有五种方法,如下所示:
-
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
接口的两种实现。 每个实现都使用不同的方法来划分事务,如表3所示:
表3. MovieDAO实现
电影DAOImpl | 电影DAOImplJTA | |
---|---|---|
实现MovieDAO接口? | 是 | 是 |
通过JNDI获取数据源? | 是 | 是 |
从数据源获取java.sql.Connection对象? | 是 | 是 |
DAO在内部划分交易? | 是 | 没有 |
使用JDBC事务? | 是 | 没有 |
使用XA数据源? | 没有 | 是 |
参与JTA交易吗? | 没有 | 是 |
MovieDAO演示应用程序
该演示应用程序是一个名为daoexamples.moviedemo.DemoServlet
的servlet类。 DemoServlet
使用两个Movie DAO来查询和更新表中的电影数据。
该servlet演示了如何在单个事务中结合支持JTA的MovieDAO
和Java Message Service,如清单8所示。
清单8.在单个事务中结合MovieDAO和JMS代码
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();
要运行演示应用程序,请在应用程序服务器中配置XA数据源和非XA数据源。 然后,部署daoexamples.ear文件。 该应用程序将在任何符合J2EE 1.3的应用程序服务器中运行。 请参阅相关主题 ,以获得EAR文件和源代码。
结论
如本文所示,实现DAO模式不仅仅需要编写底层数据访问代码。 您可以通过选择适合您的应用程序的事务划分策略,在DAO类中合并日志记录以及遵循一些简单的异常处理准则来开始构建更好的DAO。
翻译自: https://www.ibm.com/developerworks/java/library/j-dao/index.html