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 應用程式的,舉一個常用的例子, 網頁瀏覽器與用戶端互動的 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 usero password - password of the database usero 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 runUsing CATALINA_BASE:/home/jmesnil/lib/tomcatUsing CATALINA_HOME:/home/jmesnil/lib/tomcatUsing CATALINA_TMPDIR:/home/jmesnil/lib/tomcat/tempUsing JAVA_HOME:/usr/local/javaMay 6, 2003 5:56:00 PM org.apache.commons.modeler.Registry loadRegistryINFO:Loading registry informationMay 6, 2003 5:56:00 PM org.apache.commons.modeler.Registry getRegistryINFO:Creating new Registry instanceMay 6, 2003 5:56:00 PM org.apache.commons.modeler.Registry getServerINFO:Creating MBeanServerMay 6, 2003 5:56:07 PM org.apache.coyote.http11.Http11Protocol initINFO:Initializing Coyote HTTP/1.1 on port 8080Starting service Tomcat-StandaloneApache 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 的檔案和網站上找到。