J2EE 除了提供了 servlet 之外,還提供了大量的其它功能。Servlet 開發者 們也許難得使用這些功能,不情願也沒有時間用一個超出所需的大型 J2EE 服務 器來替換自己的簡單的 servlet。然而,依據J2EE 的模塊化特征,有可能將負責 特定 J2EE 功能的小組件整合到 servlet 容器裡,以此來增強 WEB 應用程序。 其中之一就是事務。有關 J2EE 事務的完整描述,您可以參考Onjava上的其他三 篇文章,現在只需知道事務是資源的操作步驟(例如:數據庫),它由四個屬性定義 ,這四個屬性根據其首字母濃縮為 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. 完成事務:如果所有事件完成,提交事務。否則,回滾事務。
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 驅動程序(見Resources )。默認情況下,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
鍵入URLhttp://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 應用程序。
1.完全分布式事務支持.如果數據層、業務層、表示層運行在不同的 JVM 上, 則有可能有一個全程的事務跨度這些JVM,事務的內容在 RMI/JRMP 和 RMI/IIOP 上傳播。
2.整合 JDBC。使用的 XAPool例子就是一個 XA-兼容的 JDBC 連接池,可以與 數據庫相互操作。XAPool 類似於 Jakarta DBCP,只是增加了 XA-兼容的特征, 如果要結合 JDBC 使用 JTA 事務就必須遵從這個特征。
3.整合 JMS。JOTM 可以結合 JORAM,由ObjectWeb 協會開發的“JMS 提供者 ”提供了事務的 JMS 消息。你可以得到出現在 servlet中同一事務的 JMS 消息 發送件和更新的數據庫。
4.WEB 服務事務。JOTM 提供了BTP(Business Transaction Protocol)、 JOTM-BTP接口,它們用於在 WEB 服務中增加事務行為。
所有這些功能的樣例和文檔都可以在 JOTM 的檔案和網站上找到。