简介
j2ee 除了提供了 servlet 之外,还提供了大量的其它功能。servlet 开发者们也许难得使用这些功能,不情愿也没有时间用一个超出所需的大型 j2ee 服务器来替换自己的简单的 servlet。然而,依据j2ee 的模块化特征,有可能将负责特定 j2ee 功能的小组件整合到 servlet 容器里,以此来增强 web 应用程序。其中之一就是事务。
有关 j2ee 事务的完整描述,您可以参考three onjava articles。现在只需知道事务是资源的操作步骤(例如:数据库),它由四个属性定义,这四个属性根据其首字母浓缩为 acid:
原子性
事务的操作,或者是全部成功(此时提交事务),或者是全部不成功(此时回滚事务),谓之为 all-or-nothing 属性。一个事务应该被视为单个工作单元,在一个事务里面绝对不可能同时存在完成了的和没有完成的操作。
一致性
完成了的事务将资源从一个有效状态转变为另一个有效状态。一致性的具体例子有:数据库的参照完整性和表中的主键唯一性。
独立性
在事务没有提交之前,事务作用的共享资源的改变在事务之外是不可见的。独立性确保了不同事务不会同时访问正在更新的数据。
持久性
由事务提交的改变会永久存在。
jotm (java open transaction manager)是由objectweb协会开发的功能完整的且资源开放的独立的事务管理器。它提供了 java 应用程序的事务支持,而且与 jta( java 事务 api)兼容。您可以在jotm home page了解到更多的详细信息。在 tomcat(或其它 servlet 容器)整合了 jotm 后,jsp 和 servlet 的开发者们就可以获得事务的优势轻而易举的创建更多健壮的 web 应用程序。
为了突出事务是怎样增强 web 应用程序的,举一个常用的例子, web 浏览器与客户端交互的 atm 。
atm 样例:
情景
此例比较简单:一个客户想从 atm 提款,输入了他的客户名称,john_doe;想提款数,$50。如果他的银行帐户上有足够的钱并且在 atm 机上有足够的现金的话,应用程序就能给他相当数目的现金,并从银行帐户上提出同样的数目。否则,操作中断,并且除出现错误信息之外,其他都不会改变。我们无需担心安全问题,只是在猜想用户是否正确授权。
这是一个非常简单的例子,但是如果不使用事务,用别的方法执行起来将会很难。客户端操作将会涉及到两个不同的资源:atm 和客户银行帐号。它们会自动的在应用程序设计中产生 acid 问题。例如:如果在 atm 上操作成功而在银行帐户上却失败(也许是因为交流失败),客户将会取到钱,但是他的帐户将不会更新。对于银行来说,这就亏大了。
更糟的是,如果银行帐户更新了,但是由于一个错误阻止 atm 传送钱,客户得不到现金,但是帐户上却提掉了这笔款。
为了防止出现上述事故,在你的应用程序里,你能够 1) 联系两个资源,并告知两者客户执行的所有当前操作,2) 询问两者是否能执行操作,3)如果两者都同意,则请求操作。即使这样,此方法也不能谓之足够健壮,因为,如果客户帐户上的钱在第二步和第三步的时候被另外一操作提走,提款可能会失败,例如,客户帐户不能出现逆差。
事务能使应用程序更简单更健壮的之处就是:在同一事务的两个资源上执行所有的操作的时候,它将会解决 acid 的问题(尤其是原子性)。
应用程序设计
数据层
在数据层,有两个不同的数据库,并各自有一张表。为了使例子更接近实际,我们使用两个不同的数据库,因为有可能从 atm 提走不是属于该客户帐户的款(请参见下文配置数据库)。
• banktest 包含代表客户帐号的 account 表。
• atmtest包含代表 atm 的 atm 表。
逻辑层
在逻辑层,有三个类来访问资源和执行操作:
• foo.bankaccount 代表给定客户的银行帐号 account,并能通
过 jdbc在 account 执行数据库操作。
• bar.atm 代表 atm,并在 atm 表上执行 jdbc 操作。
• bar.cashdelivery 使用前面两个类来执行一个客户操作。
所有逻辑在 cashdelivery.java 的 delivercash 方法中实现。
javax.transaction.usertransaction 接口用于划分事务所有 utx.begin() 和 utx.commit() (或 utx.rollback())之间的操作在同一事务内执行。这确保了应用程序不会受到如前述的遭遇。
事务使得应用程序更为简单,由以下简单的步骤组成:
1. 开始事务。
2. 联系客户的银行帐户并从帐户上提款。
3. 告诉 atm 传送钱。
4. 完成事务。
o 如果所有事件完成,提交事务。
o 否则,回滚事务。
5. 报告客户事务结果。如果事务成功,现金将被提出,钱数也将从帐户上提出。否则,一切都不会改变。
例 1。. cashdelivery.java
public boolean deliver(string client, int value) {
initialcontext ctx = new initialcontext();
usertransaction utx = (usertransaction)
ctx.lookup("java:comp/usertransaction");
...
boolean success = false;
try {
// 开始事务
utx.begin();
//联系客户银行帐户...
bankaccount account = new bankaccount(client);
// ... 从帐户上提款
account.withdraw(value);
//联系 atm...
atm atm = new atm();
// ... 传送现金给客户
atm.delivercash(value);
//一切正常
success = true;
} catch (exception e) {
// 出现故障,不得不
// 报告给客户
explanation += e.getmessage();
} finally {
try {
if (success) {
/*一切正常提交事务
直到现在,钱才真正的从帐户上提出,并且将现金传送给客户。
*/
utx.commit();
} else {
/* 出现故障,就回滚事务。
*所有在事务内处理的操作不会发生。
*/
utx.rollback();
}
} catch (exception e) {
/* 在完成事务的过程中出现故障,
*仍旧保证
* 事务内的操作不会发生。/
*/
// 报告给客户
explanation += "\n" + e.getmessage();
//最后,事务不会成功
success = false;
} finally {
return success;
}
}
}
表示层
在表示层,就用程序由两个 jsp 文件组成:
• atm.jsp, 应用程序,它发送给bar.cashdelivery 类客户登录和提款数目,并显示客户操作的结果 。
• admin.jsp,,用于显示和更新两个资源的信息。(它不属于应用程序设计的部分,但是添加它来简化资源更新,比如处理客户帐户的钱数。)
图1 ,应用程序设计
配置数据库
关于数据库,建议使用 mysql 4.0.12 和相应的 jdbc 驱动程序。默认情况下,mysql 表不会受影响。为支持事务,表在创建的时候设置为 innodb 类型。另外,为启用 innodb 类型,您可以将 mysql 配置文件内的 #skip-innodb 行注释掉。
已配置了一个 mysql 的例子,用户名为 javauser,密码为 javadude。确保该用户已被创建并且拥有创建数据库的权限。
创建数据库和表的脚本在 scripts/ 目录下的 example file 内含有。它将创建一个 account 表并插入两个客户:
• john_doe 他的帐户金额为 $100。
• jane_doe 他的帐户金额为 $600。
例2 创建 account 表
mysql> create database banktest;
mysql> use banktest;
mysql> create table account(
-> client varchar(25) not null primary key,
-> money int) type=innodb;
mysql> insert into account values("john_doe", 100);
mysql> insert into account values("jane_doe", 600);
mysql> select * from account;
+----------+-------+
client money
+----------+-------+
john_doe 100
jane_doe 600
+----------+-------+
脚本还会创建有 $500 可用现金的 atm 表。
例3 创建 atm 表
mysql> create database atmtest;
mysql> use atmtest;
mysql> create table atm(
-> id int not null auto_increment primary key,
-> cash int) type=innodb;
mysql> insert into atm values(null, 500);
mysql> select * from atm;
+----+------+
id cash
+----+------+
1 500
+----+------+
最后,复制 $catalina_home/shared/lib 内的 jdbc 驱动程序 .jar 文件。
获取并安装 tomcat
本章主要介绍 tomcat 4.1.18 及以上的版本。首先确保没有使用以前的旧版本,安装 tomcat 没有什么特别,只需下载并解压缩即可。
获取并安装 jotm
如果要使用 jotm,只需要下载最近的二元版本并将解压缩即可。再从 lib/ 目录下将.jar 文件(除了 log4j.jar、ommons-cli.jar 和 jotm_iiop_stubs.jar) 复制到 $catalina_home/shared/lib。这样就完成了。
配置 tomcat
需要配置 tomcat,使之能够从 jndi 获取 usertransaction 和 datasource 对象(它们用在 foo.bankaccount 和 bar.atm)。
首先,告诉 tomcat 你所使用的 jndi 名字,以便在 web 应用程序中查询数据源。这些步骤由 web.xml 完成,其代码如下。对于银行帐户数据源,使用的 jndi 名字是 java:comp/env/jdbc/bankaccount ,而且只能在 java:comp/env/ 之后给出名字。tomcat 通过 jndi 机制来解决其余的问题。对于 atm 数据源也同样于此。
例 4. web.xml
<web-app>
<resource-env-ref>
<description>bank account datasource</description>
<resource-env-ref-name>jdbc/bankaccount</resource-env-ref-name>
<resource-env-ref-type>javax.sql.datasource</resource-env-ref-type>
</resource-env-ref>
<resource-env-ref>
<description>atm datasource</description>
<resource-env-ref-name>jdbc/atm</resource-env-ref-name>
<resource-env-ref-type>javax.sql.datasource</resource-env-ref-type>
</resource-env-ref>
</web-app>
您必须告诉 tomcat 怎么样返回 web.xml内的资源,这个过程就由bank.xml文件来完成了。对于银行帐户和 atm 资源,您必须设置参数,以便 tomcat 能将 web 应用程序与数据源正确相连。有关更多的详细信息,请参考 tomcat jndi 数据源基础知识。(参见 resources)
其中一个参数需特别关注:factory.类中设置这个参数,用于当 web 应用程序通过 jndi 查询时来创建一个数据源。另外一个重要的资源(在 web.xml 中有描述)是 usertransaction。java:comp/usertransaction 使用这个资源来区分事务,它由 jotm 来执行。
例 5 bank.xml
<context path="/bank" docbase="bank.war" debug="0" reloadable="true" crosscontext="true">
<!-- description of the datasource "jdbc/bankaccount" -->
<resource name="jdbc/bankaccount" auth="container" type="javax.sql.datasource" />
<resourceparams name="jdbc/bankaccount">
<parameter>
<!-- factory of the datasource -->
<name>factory</name>
<value>org.objectweb.jndi.datasourcefactory</value>
</parameter>
<parameter>
<name>url</name>
<value>jdbc:mysql://localhost/banktest</value>
</parameter>
<!-- other parameters include:
o username - name of database user
o password - password of the database user
o driverclassname - jdbc driver name
-->
...
</resourceparams>
<!-- description of the datasource "jdbc/atm" -->
<resource name="jdbc/atm" auth="container" type="javax.sql.datasource" />
<!-- same type of parameters than for resource "jdbc/bankaccount" -->
<resourceparams name="jdbc/atm">
...
</resourceparams>
<!-- description of the resource "usertransaction -->
<resource name="usertransaction" auth="container" type="javax.transaction.usertransaction" />
<resourceparams name="usertransaction">
<parameter>
<name>factory</name>
<value>org.objectweb.jotm.usertransactionfactory</value>
</parameter>
<parameter>
<name>jotm.timeout</name>
<value>60</value>
</parameter>
</resourceparams>
</context>
展开 web 应用程序
一旦你设置了 jotm 和tomcat ,展开并使用 web 应用程序就很容易了。首先,下载 bank.tgz 并将之解压缩, 再将bank.xml 和 bank.war 复制到 $catalina_home/webapps 下;然后,启动 tomcat:
> cd $catalina_home/bin
> ./catalina.sh run
using catalina_base:/home/jmesnil/lib/tomcat
using catalina_home:/home/jmesnil/lib/tomcat
using catalina_tmpdir:/home/jmesnil/lib/tomcat/temp
using java_home:/usr/local/java
may 6, 2003 5:56:00 pm org.apache.commons.modeler.registry loadregistry
info:loading registry information
may 6, 2003 5:56:00 pm org.apache.commons.modeler.registry getregistry
info:creating new registry instance
may 6, 2003 5:56:00 pm org.apache.commons.modeler.registry getserver
info:creating mbeanserver
may 6, 2003 5:56:07 pm org.apache.coyote.http11.http11protocol init
info:initializing coyote http/1.1 on port 8080
starting service tomcat-standalone
apache tomcat/4.1.24-le-jdk14
您会在日志里面发现 jotm 还没有启动。它是在当您第一次访问 datasource 时才会启动的,在那个时候,您将会发现以下信息:
may 6, 2003 5:56:20 pm org.objectweb.jotm.jotm <init>
info:jotm started with a local transaction factory that is not bound.
may 6, 2003 5:56:20 pm org.objectweb.jotm.jotm <init>
info:carol initialization
键入 url http://localhost:8080/bank/ 来使用 web 应用程序。
使用 web 应用程序
web 应用程序的首页 包含两个链接:
1. 是 cash delivery 页面,您可以在上面像在 atm 一样提款。
图2 cash delivery 页面
2. 是management console,您在上面可以对 atm 或自己创建的银行帐户进行检测或更新。
图3 management console
操作之前,atm 有$500,john doe 银行帐户上有 $100 ,jane doe 银行帐户有 $600 。
如果 john doe想取 $400 ,交易将会失败,因为在他的帐户上余额不够。结果将是:
client id:john_doe, value: $400
cash can not be delivered to you
because:not enough money in your account (only $100).
如果 jane doe想取 $550 ,交易也会失败,因为atm上的现金不够。结果将是:
client id:jane_doe, value: $550
cash can not be delivered to you
because:not enough cash available from this atm (only
$500).
如果 john doe 取 $50 的话,交易将会成功。结果将是:
client id:john_doe, value: $50
please take your cash ($50)
thank you!
总结
这个简单的例子证明了 servlet 是怎样通过使用事务提供健壮和简化的,并且确保在任何情况下都正确。tomcat 和 jotm 完美的结合使在 servlet 内能轻而易举的取得事务的优势。
除上述简单的例子以外,jotm还有更多的优点。jotm 提供了以下性能,有助于增强 web 应用程序。
• 完全分布式事务支持.如果数据层、业务层、表示层运行在不同的 jvm 上,则有可能有一个全程的事务跨度这些jvm,事务的内容在 rmi/jrmp 和 rmi/iiop 上传播。
• 整合 jdbc。使用的 xapool例子就是一个 xa-兼容的 jdbc 连接池,可以与数据库相互操作。xapool 类似于 jakarta dbcp,只是增加了 xa-兼容的特征,如果要结合 jdbc 使用 jta 事务就必须遵从这个特征。
• 整合 jms。jotm 可以结合 joram,由objectweb 协会开发的“jms 提供者”提供了事务的 jms 消息。你可以得到出现在 servlet中同一事务的 jms 消息发送件和更新的数据库。
• web 服务事务。jotm 提供了btp(business transaction protocol)、jotm-btp接口,它们用于在 web 服务中增加事务行为。
所有这些功能的样例和文档都可以在 jotm 的档案和网站上找到。
matrix开源技术经onjava授权翻译并发布.
如果你对此文章有任何看法或建议,请到matrix论坛发表您的意见.
注明: 如果对matrix的翻译文章系列感兴趣,请点击oreilly和javaworld文章翻译计划查看详细情况
您也可以点击-petrel查看翻译作者的详细信息.
Java Asp PHP .Net XML C/C++ CGI VB Jsp J2ee J2se J2me EJB Servlet Tomcat Resin Struts Weblogic Eclipse ANT GUI JMS Web servise IDEA Webphere Hibernate Spring Jboss Applet Swing Socket Javamail Perl Ajax P2P 安全 模式 框架 测试 开源 游戏
Windows XP Windows 2000 Windows 2003 Windows Me Windows 9.x Linux UNIX 注册表 操作系统 服务器 应用服务器