時間:2005-12-26 作者:Binildas Christudas 瀏覽次數(shù): 3414 本文關(guān)鍵字:Web Services, Java, transactions, spring, hibernate, EJB, 事務(wù) |
|
本文主要探討如何利用Spring來裝配組件,包括其事務(wù)上下文。從J2EE應(yīng)用程序內(nèi)部連接到單個的數(shù)據(jù)庫并不是什么難事。但是,如果要裝配或者集成企業(yè)級的組件,情況就復(fù)雜了。一個組件可以有一個或多個支持它的數(shù)據(jù)庫,因此,當裝配兩個或更多的組件時,我們希望能夠保持在跨組件的多個數(shù)據(jù)庫中進行的操作的原子性。J2EE服務(wù)器為這些組件提供了一個容器來保證事務(wù)原子性和跨組件獨立性。如果使用的不是J2EE服務(wù)器,則可以利用Spring來幫助我們。Spring基于Inversion of Control(控制反轉(zhuǎn))模式(也稱為依賴注入),它不僅可以連接組件服務(wù),還可以連接關(guān)聯(lián)的事務(wù)上下文。在本文中,我們將Hibernate用作對象/關(guān)系持久性存儲和查詢服務(wù)?! ?nbsp;
裝配組件事務(wù)
假設(shè)在企業(yè)組件庫里,我們已經(jīng)有一個審計組件,里面有可以被客戶端調(diào)用的服務(wù)方法。然后,當我們想要構(gòu)建一個訂單處理系統(tǒng)時,我們發(fā)現(xiàn)存在這樣的設(shè)計要求:OrderListManager組件服務(wù)同樣需要審計組件服務(wù)。OrderListManager創(chuàng)建和管理訂單,因此所有的OrderListManager服務(wù)都有自己的事務(wù)屬性。當我們從OrderListManager服務(wù)內(nèi)調(diào)用審計組件時,我們實際上是在把OrderListManager服務(wù)的事務(wù)上下文傳播給審計服務(wù)。也許將來新的業(yè)務(wù)服務(wù)組件同樣需要審計組件,但那時將在一個不同的事務(wù)上下文中調(diào)用它。實際結(jié)果就是,即使審計組件的功能保持不變,它也可能是由別的業(yè)務(wù)服務(wù)功能組成,包含了混搭的(mix-and-match)事務(wù)屬性來提供不同的運行時事務(wù)性行為。
在圖1中有兩個獨立的調(diào)用上下文流程。在流程1里,如果客戶端有TX上下文,那么OrderListManager既可以參與其中,也可以啟動一個新的TX,這取決于客戶端是否在TX中,以及為OrderListManager方法指定了什么樣的TX屬性。這同樣適用于OrderListManager服務(wù)依次調(diào)用AuditManager方法的情況。
圖1 裝配組件事務(wù)
EJB架構(gòu)允許組件裝配者聲明式地給出正確的事務(wù)屬性,從而為他們提供這種靈活性。我們不探討聲明式事務(wù)管理的替代方案(即所謂的編程式事務(wù)控制),因為這會牽涉到代碼更改,從而產(chǎn)生不同的運行時事務(wù)行為。幾乎所有的J2EE應(yīng)用服務(wù)器都按照X/Open XA規(guī)范提供了服從兩階段提交協(xié)議的分布式事務(wù)管理器?,F(xiàn)在的問題是,我們能不能利用EJB服務(wù)器來實現(xiàn)相同的功能?Spring就是其中的一種解決方案。讓我們來看一下Spring如何幫助我們解決事務(wù)組裝的問題:
使用Spring進行事務(wù)管理
我們將看到一個輕量級的事務(wù)基礎(chǔ)架構(gòu),它實際上可以管理組件級的事務(wù)裝配。Spring是其中的一個解決方案。它的優(yōu)點在于,我們不會被捆綁到J2EE容器服務(wù)(如JNDI DataSource)上。最棒的一點是,如果我們想把這個輕量級事務(wù)基礎(chǔ)架構(gòu)關(guān)聯(lián)到一個已可用的J2EE容器基礎(chǔ)架構(gòu),將不會有任何問題??雌饋砦覀兛梢岳脙烧叩膬?yōu)點。
另一方面,Spring這個輕量級事務(wù)基礎(chǔ)架構(gòu)使用了一個面向方面編程(Aspect-Oriented Programming,AOP)框架。Spring AOP框架使用了一個支持AOP的Spring bean工廠。在特定于Spring的配置文件applicationContext.xml中,通過在組件服務(wù)級指定事務(wù)特性來劃分事務(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接口的一個特定實現(xiàn)就會截獲并解釋它們。該接口如下:
public interface PlatformTransactionManager{TransactionStatus getTransaction(TransactionDefinition definition);void commit(TransactionStatus status);void rollback(TransactionStatus status);}
Hibernate事務(wù)管理器
由于我們已決定使用Hibernate作為ORM工具,下一步要做的就是配置一個特定于Hibernate的事務(wù)管理器實現(xiàn)。
<beans><!-- other code goes here... --><bean id="transactionManager1"class="org.springframework.orm.hibernate.HibernateTransactionManager"><property name="sessionFactory"><ref local="sessionFactory1"/></property></bean></beans>
設(shè)計多個組件中的事務(wù)的管理
現(xiàn)在,我們來討論什么是“裝配組件事務(wù)”。您也許注意到了為域中的服務(wù)級組件OrderListManager所指定的各種TX屬性。圖2所示的業(yè)務(wù)域?qū)ο竽P停˙usiness Domain Object Model,BDOM)顯示了我們的域所確定的主要對象:
圖2 業(yè)務(wù)域?qū)ο竽P?/em>(BDOM)
圖字:Order:訂單;Audit:審計
為了更好的說明,我們來列出我們的域中的一些非功能性需求(Non-Functional Requirement,NFR):
考慮了以上要求之后,我們決定,OrderListManager服務(wù)會將所有的審計日志調(diào)用委托給已經(jīng)可用的AuditManager組件。這樣就得出了詳細設(shè)計,如圖3所示:
圖3 組件服務(wù)的設(shè)計
這里值得注意的一點是,由于我們的NFR,我們要將與OrderListManager相關(guān)的對象映射到appfuse1數(shù)據(jù)庫,而將與審計相關(guān)的對象映射到appfuse2。這樣,無論要審計什么,OrderListManager組件都會調(diào)用AuditManager組件。我們會看到,OrderListManager組件中的所有方法都應(yīng)該是事務(wù)性的,因為我們通過服務(wù)來創(chuàng)建訂單和線項目(line item)。那么AuditManager組件中的服務(wù)呢?因為它做的是審計跟蹤,我們關(guān)心的是盡可能維持長時間的審計跟蹤,并針對系統(tǒng)中所有可能的業(yè)務(wù)活動。這就產(chǎn)生了如下的需求:“即使主要的業(yè)務(wù)活動失敗了,也要進行審計跟蹤記錄”。AuditManager組件同樣要有自己的事務(wù),因為它也與自己的數(shù)據(jù)庫進行交互。如下所示:
<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>
現(xiàn)在,為了演示,我們把注意力放到createOrderList和addLineItem這兩個業(yè)務(wù)服務(wù)上。同時請注意,我們并沒有要求最佳設(shè)計策略——你可能注意到了,addLineItem方法拋出了FacadeException異常,而createOrderList卻沒有。在生產(chǎn)設(shè)計中,您也許希望每一個服務(wù)方法都可以處理異常場景。
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)建一個異常場景來進行演示,我們引入了另一種業(yè)務(wù)規(guī)則,它規(guī)定一個特定的訂單不能包含多于兩個的線項目?,F(xiàn)在應(yīng)該注意,我們是從createOrderList和addLineItem中調(diào)用auditManager.log()方法的。您應(yīng)該也注意到了為上述方法所指定的事務(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時,如果已經(jīng)運行了一個TX,bean方法就會加入到該TX中;否則的話,Spring的輕量級TX管理器就會啟動一個TX。如果在調(diào)用組件服務(wù)時我們總是希望開始新的事務(wù),那么可以利用PROPAGATION_REQUIRES_NEW屬性。
我們還指定,當方法拋出FacadeException類型的異常時,addLineItem就總是回滾事務(wù)。這就達到了另一個粒度級別:在異常場景中,我們的控制可以精細到TX的具體結(jié)束方式。前綴符號“-”指定回滾TX,而前綴符號“+”指定提交TX。
接下來的問題是,為什么我們要為log方法設(shè)置PROPAGATION_REQUIRES_NEW屬性呢?這是由我們的以下需求決定的:無論主服務(wù)方法發(fā)生什么情況,對所有創(chuàng)建訂單以及向系統(tǒng)添加線項目的嘗試都要記錄審計跟蹤。也就是說,即使在createOrderList和addLineItem的實現(xiàn)過程中出現(xiàn)了異常也要記錄審計跟蹤。這僅在啟動一個新的TX并在這個新的TX上下文中調(diào)用log的時候起作用。這就是為什么要為log設(shè)置PROPAGATION_REQUIRES_NEW TX屬性的原因:如果對下述方法的調(diào)用成功了
auditManager.log(new AuditObject(LINE_ITEM +lineItemId, CREATE));
,auditManager.log()就將在新的TX上下文中執(zhí)行,而且只要auditManager.log()本身成功(即,沒有拋出異常),新的上下文就會被提交。
設(shè)置演示環(huán)境
準備演示環(huán)境時,我參考了Spring Live這本書的流程:
<role rolename="manager"/><user username="admin"
password="admin" roles="manager"/>
圖4 Equinox的myusers應(yīng)用程序文件夾模板
運行演示
為了運行測試用例,myusers estmxampleservice中提供了一個JUnit測試類,OrderListManagerTest。要執(zhí)行它,可以在構(gòu)建應(yīng)用程序的命令提示符中輸入以下命令:
CATALINA_HOMEinnt test -Dtestcase=OrderListManager
測試用例分為兩個主要部分:第一部分創(chuàng)建一個由兩個線項目組成的訂單,然后把這兩個線項目鏈接到訂單中。它可以成功運行,如下所示:
OrderList orderList1 = new OrderList();Long orderId1 = orderListManager.createOrderList(orderList1);log("Created OrderList with id ‘"+ orderId1 + "‘...");orderListManager.addLineItem(orderId1,lineItem1);orderListManager.addLineItem(orderId1,lineItem2);
第二部分執(zhí)行類似的操作,但是這次我們試圖向訂單添加三個線項目,這將產(chǎn)生一個異常:
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,并向其添加了兩個ID為1和2的線項目。然后我們創(chuàng)建Order2,并嘗試添加3個項目,前兩個(ID為3和4)添加成功,但是圖5顯示,添加第三個項目(ID為5)時業(yè)務(wù)方法遇到了異常。因此,業(yè)務(wù)方法TX被回滾,數(shù)據(jù)庫中沒有ID為5的線項目。從控制臺執(zhí)行以下命令,就可以通過圖6和圖7進行驗證:
CATALINA_HOMEinnt browse1
圖6 appfuse1數(shù)據(jù)庫中創(chuàng)建的訂單
圖7 appfuse1數(shù)據(jù)庫中創(chuàng)建的線項目
在接下來的也是最重要的演示部分中可以看出,訂單和線項目保存在appfuse1數(shù)據(jù)庫中,而審計對象保存在appfuse2數(shù)據(jù)庫中。實際上,OrderListManager中的服務(wù)方法可以與多個數(shù)據(jù)庫交互。啟動appfuse2數(shù)據(jù)庫,查看審計跟蹤,如下所示:
CATALINA_HOMEinnt browse2
圖8 創(chuàng)建到appfuse2數(shù)據(jù)庫中的審計跟蹤,包括失敗TX的記錄項
表8最后一行尤其值得注意,RESOURCE列顯示這一行對應(yīng)的是LineItem5。但是當我們回過來看圖7時,卻發(fā)現(xiàn)并沒有對應(yīng)于LineItem5的線項目。哪里出錯了呢?事實上并沒有出錯,圖7沒有的那一行其實正是這篇文章的關(guān)鍵所在,讓我們來看看是怎么回事。
我們知道,addLineItem()方法包含PROPAGATION_REQUIRED屬性,而log()方法有PROPAGATION_REQUIRES_NEW屬性。而且addLineItem()在內(nèi)部調(diào)用了log()方法。因此,當我們試圖向Order2添加第三個線項目時,就(按照我們的業(yè)務(wù)規(guī)則)引發(fā)了異常,于是這個線項目的創(chuàng)建以及將其鏈接到Order2的操作都被回滾了。但是,因為還從addLineItem()中調(diào)用了log(),還因為log()具有PROPAGATION_REQUIRES_NEW TX屬性,回滾addLineItem()將不會造成回滾log(),因為log()是在一個新的TX中執(zhí)行。
讓我們對log()的TX屬性做一下改動,把PROPAGATION_REQUIRES_NEW替換為PROPAGATION_SUPPORTS。PROPAGATION_SUPPORTS屬性允許服務(wù)方法在客戶端有TX上下文時在客戶端TX中運行;如果客戶端沒有TX,就不用TX而直接運行。您可能必須重新安裝應(yīng)用程序,以便數(shù)據(jù)庫中已經(jīng)可用的數(shù)據(jù)可以自動刷新。重新安裝請按照“設(shè)置演示環(huán)境”中的步驟12進行。
如果再次運行,我們會發(fā)現(xiàn)一點不同。這次,在試圖向Order2添加第三個線項目時依然有異常,這將回滾試圖添加第三個線項目的事務(wù)。而這個方法又調(diào)用了log()方法。但是由于它的PROPAGATION_SUPPORTS TX屬性,log()將在與addLineItem()方法相同的TX上下文中調(diào)用。由于addLineItem()回滾,log()也回滾了,沒有留下回滾的TX的審計跟蹤。所以在圖9中沒有對應(yīng)于失敗TX的審計跟蹤記錄項!
圖9 創(chuàng)建在appfuse2數(shù)據(jù)庫中的審計跟蹤,沒有失敗TX的記錄項
我們改動的僅僅是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出現(xiàn)以來我們就一直在使用這種方法,但是我們需要一個高端的應(yīng)用服務(wù)器來駐留EJB組件?,F(xiàn)在,我們可以看到,利用Spring,沒有EJB服務(wù)器也可以達到類似的結(jié)果。
結(jié)束語
這篇文章重點介紹了J2EE領(lǐng)域的強大組合之一:Spring和Hibernate。利用二者的功能,現(xiàn)在對于容器管理持久性(Container-Managed Persistence,CMP)、容器管理關(guān)系(Container-Managed Relationships,CMR)和聲明式事務(wù)管理,我們多了一種技術(shù)選擇。雖然Spring不能視為EJB的替代方案,但是它提供的許多功能,例如普通Java對象的聲明式事務(wù)管理,使得在許多項目中沒有EJB也完全可以。
本文的目的不是要尋找EJB的替代方案,而是為當前的問題找出一個最可行的技術(shù)方案。至于Spring和Hibernate的輕量級組合的更多功能,就留給我們的讀者去探索了。
參考資料
原文出處 Wire Hibernate Transactions in Spring http://www.onjava.com/pub/a/onjava/2005/05/18/swingxactions.html
作者簡介 | |
Binildas Christudas是工程學學士和系統(tǒng)學碩士,他還是Sun公司認證的Java平臺程序員、軟件開發(fā)員和企業(yè)級的系統(tǒng)架構(gòu)師。他有6年IT行業(yè)的從業(yè)經(jīng)驗,目前供職于一家印度IT公司 —— 塔塔咨詢服務(wù)公司,為瑞士航空、Sabena航空、新加坡航空、荷蘭皇家航空公司等提供咨詢服務(wù)。對于許多新一代航空系統(tǒng)的設(shè)計和開發(fā),他的經(jīng)驗非常豐富。 |