| 時(shí)間:2005-12-26 作者:Binildas Christudas 瀏覽次數: 3414 本文關(guān)鍵字:Web Services, Java, transactions, spring, hibernate, EJB, 事務(wù) |
|

本文主要探討如何利用Spring來(lái)裝配組件,包括其事務(wù)上下文。從J2EE應用程序內部連接到單個(gè)的數據庫并不是什么難事。但是,如果要裝配或者集成企業(yè)級的組件,情況就復雜了。一個(gè)組件可以有一個(gè)或多個(gè)支持它的數據庫,因此,當裝配兩個(gè)或更多的組件時(shí),我們希望能夠保持在跨組件的多個(gè)數據庫中進(jìn)行的操作的原子性。J2EE服務(wù)器為這些組件提供了一個(gè)容器來(lái)保證事務(wù)原子性和跨組件獨立性。如果使用的不是J2EE服務(wù)器,則可以利用Spring來(lái)幫助我們。Spring基于Inversion of Control(控制反轉)模式(也稱(chēng)為依賴(lài)注入),它不僅可以連接組件服務(wù),還可以連接關(guān)聯(lián)的事務(wù)上下文。在本文中,我們將Hibernate用作對象/關(guān)系持久性存儲和查詢(xún)服務(wù)?! ?nbsp;
裝配組件事務(wù)
假設在企業(yè)組件庫里,我們已經(jīng)有一個(gè)審計組件,里面有可以被客戶(hù)端調用的服務(wù)方法。然后,當我們想要構建一個(gè)訂單處理系統時(shí),我們發(fā)現存在這樣的設計要求:OrderListManager組件服務(wù)同樣需要審計組件服務(wù)。OrderListManager創(chuàng )建和管理訂單,因此所有的OrderListManager服務(wù)都有自己的事務(wù)屬性。當我們從OrderListManager服務(wù)內調用審計組件時(shí),我們實(shí)際上是在把OrderListManager服務(wù)的事務(wù)上下文傳播給審計服務(wù)。也許將來(lái)新的業(yè)務(wù)服務(wù)組件同樣需要審計組件,但那時(shí)將在一個(gè)不同的事務(wù)上下文中調用它。實(shí)際結果就是,即使審計組件的功能保持不變,它也可能是由別的業(yè)務(wù)服務(wù)功能組成,包含了混搭的(mix-and-match)事務(wù)屬性來(lái)提供不同的運行時(shí)事務(wù)性行為。
在圖1中有兩個(gè)獨立的調用上下文流程。在流程1里,如果客戶(hù)端有TX上下文,那么OrderListManager既可以參與其中,也可以啟動(dòng)一個(gè)新的TX,這取決于客戶(hù)端是否在TX中,以及為OrderListManager方法指定了什么樣的TX屬性。這同樣適用于OrderListManager服務(wù)依次調用AuditManager方法的情況。

圖1 裝配組件事務(wù)
EJB架構允許組件裝配者聲明式地給出正確的事務(wù)屬性,從而為他們提供這種靈活性。我們不探討聲明式事務(wù)管理的替代方案(即所謂的編程式事務(wù)控制),因為這會(huì )牽涉到代碼更改,從而產(chǎn)生不同的運行時(shí)事務(wù)行為。幾乎所有的J2EE應用服務(wù)器都按照X/Open XA規范提供了服從兩階段提交協(xié)議的分布式事務(wù)管理器?,F在的問(wèn)題是,我們能不能利用EJB服務(wù)器來(lái)實(shí)現相同的功能?Spring就是其中的一種解決方案。讓我們來(lái)看一下Spring如何幫助我們解決事務(wù)組裝的問(wèn)題:
使用Spring進(jìn)行事務(wù)管理
我們將看到一個(gè)輕量級的事務(wù)基礎架構,它實(shí)際上可以管理組件級的事務(wù)裝配。Spring是其中的一個(gè)解決方案。它的優(yōu)點(diǎn)在于,我們不會(huì )被捆綁到J2EE容器服務(wù)(如JNDI DataSource)上。最棒的一點(diǎn)是,如果我們想把這個(gè)輕量級事務(wù)基礎架構關(guān)聯(lián)到一個(gè)已可用的J2EE容器基礎架構,將不會(huì )有任何問(wèn)題??雌饋?lái)我們可以利用兩者的優(yōu)點(diǎn)。
另一方面,Spring這個(gè)輕量級事務(wù)基礎架構使用了一個(gè)面向方面編程(Aspect-Oriented Programming,AOP)框架。Spring AOP框架使用了一個(gè)支持AOP的Spring bean工廠(chǎng)。在特定于Spring的配置文件applicationContext.xml中,通過(guò)在組件服務(wù)級指定事務(wù)特性來(lái)劃分事務(wù)。
<beans><!-- other code goes here... --><bean id="orderListManager"class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"><property name="transactionManager"><ref local="transactionManager1"/></property><property name="target"><ref local="orderListManagerTarget"/></property><property name="transactionAttributes"><props><prop key="getAllOrderList">PROPAGATION_REQUIRED</prop><prop key="getOrderList">PROPAGATION_REQUIRED</prop><prop key="createOrderList">PROPAGATION_REQUIRED</prop><prop key="addLineItem">PROPAGATION_REQUIRED,-com.example.exception.FacadeException</prop><prop key="getAllLineItems">PROPAGATION_REQUIRED,readOnly</prop><prop key="queryNumberOfLineItems">PROPAGATION_REQUIRED,readOnly</prop></props></property></bean></beans>
一旦我們在服務(wù)級指定了事務(wù)屬性,org.springframework.transaction.PlatformTransactionManager接口的一個(gè)特定實(shí)現就會(huì )截獲并解釋它們。該接口如下:
public interface PlatformTransactionManager{TransactionStatus getTransaction(TransactionDefinition definition);void commit(TransactionStatus status);void rollback(TransactionStatus status);}Hibernate事務(wù)管理器
由于我們已決定使用Hibernate作為ORM工具,下一步要做的就是配置一個(gè)特定于Hibernate的事務(wù)管理器實(shí)現。
<beans><!-- other code goes here... --><bean id="transactionManager1"class="org.springframework.orm.hibernate.HibernateTransactionManager"><property name="sessionFactory"><ref local="sessionFactory1"/></property></bean></beans>
設計多個(gè)組件中的事務(wù)的管理
現在,我們來(lái)討論什么是“裝配組件事務(wù)”。您也許注意到了為域中的服務(wù)級組件OrderListManager所指定的各種TX屬性。圖2所示的業(yè)務(wù)域對象模型(Business Domain Object Model,BDOM)顯示了我們的域所確定的主要對象:

圖2 業(yè)務(wù)域對象模型(BDOM)
圖字:Order:訂單;Audit:審計
為了更好的說(shuō)明,我們來(lái)列出我們的域中的一些非功能性需求(Non-Functional Requirement,NFR):
考慮了以上要求之后,我們決定,OrderListManager服務(wù)會(huì )將所有的審計日志調用委托給已經(jīng)可用的AuditManager組件。這樣就得出了詳細設計,如圖3所示:

圖3 組件服務(wù)的設計
這里值得注意的一點(diǎn)是,由于我們的NFR,我們要將與OrderListManager相關(guān)的對象映射到appfuse1數據庫,而將與審計相關(guān)的對象映射到appfuse2。這樣,無(wú)論要審計什么,OrderListManager組件都會(huì )調用AuditManager組件。我們會(huì )看到,OrderListManager組件中的所有方法都應該是事務(wù)性的,因為我們通過(guò)服務(wù)來(lái)創(chuàng )建訂單和線(xiàn)項目(line item)。那么AuditManager組件中的服務(wù)呢?因為它做的是審計跟蹤,我們關(guān)心的是盡可能維持長(cháng)時(shí)間的審計跟蹤,并針對系統中所有可能的業(yè)務(wù)活動(dòng)。這就產(chǎn)生了如下的需求:“即使主要的業(yè)務(wù)活動(dòng)失敗了,也要進(jìn)行審計跟蹤記錄”。AuditManager組件同樣要有自己的事務(wù),因為它也與自己的數據庫進(jìn)行交互。如下所示:
<beans><!-- other code goes here... --><bean id="auditManager"class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"><property name="transactionManager"><ref local="transactionManager2"/></property><property name="target"><ref local="auditManagerTarget"/></property><property name="transactionAttributes"><props><prop key="log">PROPAGATION_REQUIRES_NEW</prop></props></property></bean></beans>
現在,為了演示,我們把注意力放到createOrderList和addLineItem這兩個(gè)業(yè)務(wù)服務(wù)上。同時(shí)請注意,我們并沒(méi)有要求最佳設計策略——你可能注意到了,addLineItem方法拋出了FacadeException異常,而createOrderList卻沒(méi)有。在生產(chǎn)設計中,您也許希望每一個(gè)服務(wù)方法都可以處理異常場(chǎng)景。
public class OrderListManagerImplimplements OrderListManager{private AuditManager auditManager;public Long createOrderList(OrderList orderList){Long orderId = orderListDAO.createOrderList(orderList);auditManager.log(new AuditObject(ORDER + orderId, CREATE));return orderId;}public void addLineItem(Long orderId, LineItem lineItem)throws FacadeException{Long lineItemId = orderListDAO.addLineItem(orderId, lineItem);auditManager.log(new AuditObject(LINE_ITEM + lineItemId, CREATE));int numberOfLineItems = orderListDAO.queryNumberOfLineItems(orderId);if(numberOfLineItems > 2){log("Added LineItem " + lineItemId +" to Order " + orderId + ";But rolling back *** !");throw new FacadeException("Make a new Order for this line item");}else{log("Added LineItem " + lineItemId +" to Order " + orderId + ".");}}//Other code goes here...}為了創(chuàng )建一個(gè)異常場(chǎng)景來(lái)進(jìn)行演示,我們引入了另一種業(yè)務(wù)規則,它規定一個(gè)特定的訂單不能包含多于兩個(gè)的線(xiàn)項目?,F在應該注意,我們是從createOrderList和addLineItem中調用auditManager.log()方法的。您應該也注意到了為上述方法所指定的事務(wù)屬性。
<bean id="orderListManager"class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"><property name="transactionAttributes"><props><prop key="createOrderList">PROPAGATION_REQUIRED</prop><prop key="addLineItem">PROPAGATION_REQUIRED,-com.example.exception.FacadeException</prop></props></property></bean><bean id="auditManager" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"><property name="transactionAttributes"><props><prop key="log">PROPAGATION_REQUIRES_NEW</prop></props></property></bean>
PROPAGATION_REQUIRED等效于TX_REQUIRED,而PROPAGATION_REQUIRES_NEW等效于EJB中的TX_REQUIRES_NEW。如果我們想讓服務(wù)方法始終在事務(wù)中運行,我們可以使用PROPAGATION_REQUIRED。當使用PROPAGATION_REQUIRED時(shí),如果已經(jīng)運行了一個(gè)TX,bean方法就會(huì )加入到該TX中;否則的話(huà),Spring的輕量級TX管理器就會(huì )啟動(dòng)一個(gè)TX。如果在調用組件服務(wù)時(shí)我們總是希望開(kāi)始新的事務(wù),那么可以利用PROPAGATION_REQUIRES_NEW屬性。
我們還指定,當方法拋出FacadeException類(lèi)型的異常時(shí),addLineItem就總是回滾事務(wù)。這就達到了另一個(gè)粒度級別:在異常場(chǎng)景中,我們的控制可以精細到TX的具體結束方式。前綴符號“-”指定回滾TX,而前綴符號“+”指定提交TX。
接下來(lái)的問(wèn)題是,為什么我們要為log方法設置PROPAGATION_REQUIRES_NEW屬性呢?這是由我們的以下需求決定的:無(wú)論主服務(wù)方法發(fā)生什么情況,對所有創(chuàng )建訂單以及向系統添加線(xiàn)項目的嘗試都要記錄審計跟蹤。也就是說(shuō),即使在createOrderList和addLineItem的實(shí)現過(guò)程中出現了異常也要記錄審計跟蹤。這僅在啟動(dòng)一個(gè)新的TX并在這個(gè)新的TX上下文中調用log的時(shí)候起作用。這就是為什么要為log設置PROPAGATION_REQUIRES_NEW TX屬性的原因:如果對下述方法的調用成功了
auditManager.log(new AuditObject(LINE_ITEM +lineItemId, CREATE));
,auditManager.log()就將在新的TX上下文中執行,而且只要auditManager.log()本身成功(即,沒(méi)有拋出異常),新的上下文就會(huì )被提交。
設置演示環(huán)境
準備演示環(huán)境時(shí),我參考了Spring Live這本書(shū)的流程:
<role rolename="manager"/><user username="admin"
password="admin" roles="manager"/>

圖4 Equinox的myusers應用程序文件夾模板
運行演示
為了運行測試用例,myusers estmxampleservice中提供了一個(gè)JUnit測試類(lèi),OrderListManagerTest。要執行它,可以在構建應用程序的命令提示符中輸入以下命令:
CATALINA_HOMEinnt test -Dtestcase=OrderListManager
測試用例分為兩個(gè)主要部分:第一部分創(chuàng )建一個(gè)由兩個(gè)線(xiàn)項目組成的訂單,然后把這兩個(gè)線(xiàn)項目鏈接到訂單中。它可以成功運行,如下所示:
OrderList orderList1 = new OrderList();Long orderId1 = orderListManager.createOrderList(orderList1);log("Created OrderList with id ‘"+ orderId1 + "‘...");orderListManager.addLineItem(orderId1,lineItem1);orderListManager.addLineItem(orderId1,lineItem2);第二部分執行類(lèi)似的操作,但是這次我們試圖向訂單添加三個(gè)線(xiàn)項目,這將產(chǎn)生一個(gè)異常:
OrderList orderList2 = new OrderList();Long orderId2 = orderListManager.createOrderList(orderList2);log("Created OrderList with id ‘" + orderId2 + "‘...");orderListManager.addLineItem(orderId2,lineItem3);orderListManager.addLineItem(orderId2,lineItem4);//We know, we will have an exception here,still want to proceedtry{orderListManager.addLineItem(orderId2,lineItem5);}catch(FacadeException facadeException){log("ERROR : " + facadeException.getMessage());}控制臺的輸出如圖5所示:

我們創(chuàng )建了Order1,并向其添加了兩個(gè)ID為1和2的線(xiàn)項目。然后我們創(chuàng )建Order2,并嘗試添加3個(gè)項目,前兩個(gè)(ID為3和4)添加成功,但是圖5顯示,添加第三個(gè)項目(ID為5)時(shí)業(yè)務(wù)方法遇到了異常。因此,業(yè)務(wù)方法TX被回滾,數據庫中沒(méi)有ID為5的線(xiàn)項目。從控制臺執行以下命令,就可以通過(guò)圖6和圖7進(jìn)行驗證:
CATALINA_HOMEinnt browse1

圖6 appfuse1數據庫中創(chuàng )建的訂單

圖7 appfuse1數據庫中創(chuàng )建的線(xiàn)項目
在接下來(lái)的也是最重要的演示部分中可以看出,訂單和線(xiàn)項目保存在appfuse1數據庫中,而審計對象保存在appfuse2數據庫中。實(shí)際上,OrderListManager中的服務(wù)方法可以與多個(gè)數據庫交互。啟動(dòng)appfuse2數據庫,查看審計跟蹤,如下所示:
CATALINA_HOMEinnt browse2

圖8 創(chuàng )建到appfuse2數據庫中的審計跟蹤,包括失敗TX的記錄項
表8最后一行尤其值得注意,RESOURCE列顯示這一行對應的是LineItem5。但是當我們回過(guò)來(lái)看圖7時(shí),卻發(fā)現并沒(méi)有對應于LineItem5的線(xiàn)項目。哪里出錯了呢?事實(shí)上并沒(méi)有出錯,圖7沒(méi)有的那一行其實(shí)正是這篇文章的關(guān)鍵所在,讓我們來(lái)看看是怎么回事。
我們知道,addLineItem()方法包含PROPAGATION_REQUIRED屬性,而log()方法有PROPAGATION_REQUIRES_NEW屬性。而且addLineItem()在內部調用了log()方法。因此,當我們試圖向Order2添加第三個(gè)線(xiàn)項目時(shí),就(按照我們的業(yè)務(wù)規則)引發(fā)了異常,于是這個(gè)線(xiàn)項目的創(chuàng )建以及將其鏈接到Order2的操作都被回滾了。但是,因為還從addLineItem()中調用了log(),還因為log()具有PROPAGATION_REQUIRES_NEW TX屬性,回滾addLineItem()將不會(huì )造成回滾log(),因為log()是在一個(gè)新的TX中執行。
讓我們對log()的TX屬性做一下改動(dòng),把PROPAGATION_REQUIRES_NEW替換為PROPAGATION_SUPPORTS。PROPAGATION_SUPPORTS屬性允許服務(wù)方法在客戶(hù)端有TX上下文時(shí)在客戶(hù)端TX中運行;如果客戶(hù)端沒(méi)有TX,就不用TX而直接運行。您可能必須重新安裝應用程序,以便數據庫中已經(jīng)可用的數據可以自動(dòng)刷新。重新安裝請按照“設置演示環(huán)境”中的步驟12進(jìn)行。
如果再次運行,我們會(huì )發(fā)現一點(diǎn)不同。這次,在試圖向Order2添加第三個(gè)線(xiàn)項目時(shí)依然有異常,這將回滾試圖添加第三個(gè)線(xiàn)項目的事務(wù)。而這個(gè)方法又調用了log()方法。但是由于它的PROPAGATION_SUPPORTS TX屬性,log()將在與addLineItem()方法相同的TX上下文中調用。由于addLineItem()回滾,log()也回滾了,沒(méi)有留下回滾的TX的審計跟蹤。所以在圖9中沒(méi)有對應于失敗TX的審計跟蹤記錄項!

圖9 創(chuàng )建在appfuse2數據庫中的審計跟蹤,沒(méi)有失敗TX的記錄項
我們改動(dòng)的僅僅是Spring配置文件中的TX屬性,就產(chǎn)生了這樣不同的事務(wù)行為,如下所示:
<bean id="auditManager" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"><property name="transactionAttributes"><props><!-- prop key="log">PROPAGATION_REQUIRES_NEW</prop --><prop key="log">PROPAGATION_SUPPORTS</prop></props></property></bean>
這就是聲明式事務(wù)管理的效果。自從EJB出現以來(lái)我們就一直在使用這種方法,但是我們需要一個(gè)高端的應用服務(wù)器來(lái)駐留EJB組件?,F在,我們可以看到,利用Spring,沒(méi)有EJB服務(wù)器也可以達到類(lèi)似的結果。
結束語(yǔ)
這篇文章重點(diǎn)介紹了J2EE領(lǐng)域的強大組合之一:Spring和Hibernate。利用二者的功能,現在對于容器管理持久性(Container-Managed Persistence,CMP)、容器管理關(guān)系(Container-Managed Relationships,CMR)和聲明式事務(wù)管理,我們多了一種技術(shù)選擇。雖然Spring不能視為EJB的替代方案,但是它提供的許多功能,例如普通Java對象的聲明式事務(wù)管理,使得在許多項目中沒(méi)有EJB也完全可以。
本文的目的不是要尋找EJB的替代方案,而是為當前的問(wèn)題找出一個(gè)最可行的技術(shù)方案。至于Spring和Hibernate的輕量級組合的更多功能,就留給我們的讀者去探索了。
參考資料
原文出處 Wire Hibernate Transactions in Spring http://www.onjava.com/pub/a/onjava/2005/05/18/swingxactions.html

| 作者簡(jiǎn)介 | |
| Binildas Christudas是工程學(xué)學(xué)士和系統學(xué)碩士,他還是Sun公司認證的Java平臺程序員、軟件開(kāi)發(fā)員和企業(yè)級的系統架構師。他有6年IT行業(yè)的從業(yè)經(jīng)驗,目前供職于一家印度IT公司 —— 塔塔咨詢(xún)服務(wù)公司,為瑞士航空、Sabena航空、新加坡航空、荷蘭皇家航空公司等提供咨詢(xún)服務(wù)。對于許多新一代航空系統的設計和開(kāi)發(fā),他的經(jīng)驗非常豐富。 | |
聯(lián)系客服