Spring Framework是一個(gè)流行的Java/J2EE應(yīng)用框架,它構(gòu)建于一個(gè)輕量級(jí)的反向控制(Inversion-of-Control,QoC)模式的容器的基礎(chǔ)之上,以其數(shù)據(jù)訪問(wèn)和事務(wù)管理能力而著稱。Spring的聲明性事務(wù)劃分適用于任何的POJO(pure old java object或plain ordinary Java object,無(wú)格式普通Java對(duì)象)目標(biāo)對(duì)象,其聲明性事務(wù)如同EJB容器托管事務(wù)(Container-Managed Transaction,CMT)一樣完善。后端事務(wù)管理器的選擇包括簡(jiǎn)單的基于JDBC的事務(wù)和完善的J2EE事務(wù)(借助于JTA策略)。
本文詳細(xì)討論了Spring的事務(wù)管理功能。重點(diǎn)介紹了如何以JTA作為后端事務(wù)策略,使用Spring的針對(duì)POJO的聲明性事務(wù)。本文說(shuō)明了Spring的事務(wù)服務(wù)可以與J2EE服務(wù)器的事務(wù)協(xié)調(diào)程序(如BEA WebLogic Server的事務(wù)協(xié)調(diào)程序)進(jìn)行無(wú)縫交互,實(shí)際上已經(jīng)成為EJB CMT的傳統(tǒng)事務(wù)劃分方式的替代方案。
針對(duì)POJO的聲明性事務(wù)
為了說(shuō)明Spring的聲明性事務(wù)劃分方式,讓我們來(lái)看看Spring的PetClinic示例應(yīng)用程序的中央服務(wù)外觀(facade)的配置:
<bean id="dataSource"class="org.springframework.jndi.JndiObjectFactoryBean"><property name="jndiName"><value>java:comp/env/jdbc/petclinic</value></property></bean><bean id="transactionManager"class="org.springframework.transaction.jta.JtaTransactionManager"/><bean id="clinicTarget"class="org.springframework.samples.petclinic.jdbc.JdbcClinic"><property name="dataSource"><ref bean="dataSource"/></property></bean><bean id="clinic"class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"><property name="transactionManager"><ref bean="transactionManager"/></property><property name="target"><ref bean="clinicTarget"/></property><property name="transactionAttributes"><props><prop key="load*">PROPAGATION_REQUIRED,readOnly</prop><prop key="store*">PROPAGATION_REQUIRED</prop></props></property></bean>
它遵從了Spring的標(biāo)準(zhǔn)XMLBean定義格式。它定義了:
注意:Spring還通過(guò)通用屬性(Commons Attribute)或者J2SE 5.0的注釋(annotation),支持一種自動(dòng)代理機(jī)制和對(duì)源級(jí)(source-level)元數(shù)據(jù)的使用,作為顯示代理定義的替代方案。這些替代方案不在本文的討論范圍之內(nèi);其詳細(xì)資料請(qǐng)參考Spring說(shuō)明文檔。
使用的服務(wù)接口和服務(wù)實(shí)現(xiàn)是特定于應(yīng)用程序的,無(wú)需了解Spring(具體說(shuō)是Spring的事務(wù)管理)就可以實(shí)現(xiàn)。純Java對(duì)象可以用作目標(biāo)對(duì)象,而任何一個(gè)純Java接口都可以用作服務(wù)接口。下面是一個(gè)Clinic接口的例子:
public interface Clinic {Pet loadPet(int id);void storePet(Pet pet);...}
下面顯示了該接口的一個(gè)簡(jiǎn)單實(shí)現(xiàn),假定它使用JDBC來(lái)執(zhí)行必要的數(shù)據(jù)訪問(wèn)。它通過(guò)一個(gè)bean屬性的setter方法接收J(rèn)DBC DataSource,這直接對(duì)應(yīng)上面配置中的dataSource屬性定義。
public class JdbcClinic implements Clinic {private DataSource dataSource;public void setDataSource(DataSource dataSource) {this.dataSource = dataSource;}public Pet loadPet(int id) {try {Connection con = this.dataSource.getConnection();...}catch (SQLException ex) {...}}public void storePet(Pet pet) {try {Connection con = this.dataSource.getConnection();...}catch (SQLException ex) {...}}...}
正如您所看到的,代碼簡(jiǎn)單明了。使用了一個(gè)簡(jiǎn)單Java對(duì)象。事務(wù)管理由事務(wù)代理處理,我們隨后再對(duì)其進(jìn)行說(shuō)明。
注意,PetClinic示例應(yīng)用程序中實(shí)際的基于JDBC的Clinic實(shí)現(xiàn)利用了Spring的JDBC支持類,以免只工作在簡(jiǎn)單的JDBC API級(jí)別上。但是,Spring的事務(wù)管理還將使用簡(jiǎn)單的基于JDBC的實(shí)現(xiàn),比如上面的實(shí)現(xiàn)。
定義事務(wù)代理
除JdbcClinic實(shí)例之外,配置還為其定義了一個(gè)事務(wù)代理。如果需要,可以顯式地指定該事務(wù)代理所暴露的實(shí)際接口。默認(rèn)狀態(tài)下,目標(biāo)對(duì)象實(shí)現(xiàn)的所有接口都將被暴露--在本例中是應(yīng)用程序的Clinic服務(wù)接口。
從客戶端的角度來(lái)看,“clinic”bean只是應(yīng)用程序的Clinic接口的實(shí)現(xiàn)??蛻舳瞬槐刂雷约赫诤褪聞?wù)代理打交道。這就是接口的力量:目標(biāo)對(duì)象的直接引用可以很輕松地由實(shí)現(xiàn)了相同接口的代理取代--在本例中是一個(gè)隱式地創(chuàng)建事務(wù)的代理。
對(duì)于特定的方法或方法命名模式,代理的具體事務(wù)行為由事務(wù)屬性驅(qū)動(dòng),如下面的例子所示:
<prop key="load*">PROPAGATION_REQUIRED,readOnly</prop><prop key="store*">PROPAGATION_REQUIRED</prop>key屬性確定代理應(yīng)該給哪個(gè)方法增加事務(wù)行為。這樣的屬性最重要的部份是傳播行為。有以下選項(xiàng)可供使用:
前六個(gè)策略類似于EJB CMT:常量名相同,因此,對(duì)EJB開(kāi)發(fā)人員來(lái)說(shuō),應(yīng)該立刻就感到熟悉。第七個(gè)(PROPAGATION_NESTED)是Spring所提供的一個(gè)特殊變量。它要求事務(wù)管理器或者使用JDBC 3.0 Savepoint API提供嵌套事務(wù)行為(如Spring的DataSourceTransactionManager),或者通過(guò)JTA支持嵌套事務(wù)。
事務(wù)屬性中的readOnly標(biāo)志表示對(duì)應(yīng)的事務(wù)應(yīng)該被最優(yōu)化為只讀事務(wù)。這是一個(gè)最優(yōu)化提示。在一些情況下,一些事務(wù)策略能夠起到顯著的最優(yōu)化效果,例如在使用Object/Relational映射工具(如:Hibernate或TopLink)時(shí)避免dirty checking(試圖“刷新”)。
在事務(wù)屬性中還有定義“timeout”值的選項(xiàng),指定事務(wù)超時(shí)為幾秒。在JTA中,這將被簡(jiǎn)單地傳遞到J2EE服務(wù)器的事務(wù)協(xié)調(diào)程序,并據(jù)此得到相應(yīng)的解釋。
使用事務(wù)代理
在運(yùn)行時(shí),客戶端將獲取到“clinic”bean的引用,將其轉(zhuǎn)換為Clinic接口,同時(shí)在它上面調(diào)用諸如loadPet或storePet之類的操作。這將隱式地使用在目標(biāo)對(duì)象之前注冊(cè)的“事務(wù)攔截器”檢查Spring的事務(wù)代理;新的事務(wù)將被創(chuàng)建,然后調(diào)用將被委派給JdbcClinic目標(biāo)方法。
圖1說(shuō)明了一個(gè)具有“advisor鏈”和終端目標(biāo)的AOP代理的底層概念。其中,唯一的advisor就是將事務(wù)行為包裝到目標(biāo)方法的事務(wù)攔截器。這是在Spring的聲明性事務(wù)功能的幫助下產(chǎn)生的基于代理的AOP(面向方面編程)。
圖1. 具有“advisor鏈”和終端目標(biāo)的AOP代理
例如,PetClinic web應(yīng)用程序中的web層組件能夠執(zhí)行ServletContext查詢操作來(lái)獲取對(duì)Spring WebApplicationContext的引用,然后獲得那里托管的“Clinic”bean:
WebApplicationContext ctx =WebApplicationContexUtils.getWebApplicationContext(servletContext);Clinic clinic = (Clinic) ctx.getBean("clinic);Pet pet = new Pet();pet.setName("my new cat");clinic.storePet(pet);
在storePet()調(diào)用的開(kāi)始,Spring的事務(wù)代理將隱式地創(chuàng)建一個(gè)事務(wù)。在storePet()調(diào)用返回時(shí),將提交或回滾事務(wù)。默認(rèn)情況下,任何RuntimeException或Error的拋出均會(huì)導(dǎo)致回滾??梢灾付ê螘r(shí)提交和何時(shí)回滾的實(shí)際規(guī)則:Spring的事務(wù)屬性支持一個(gè)稱為“回滾規(guī)則”的概念。
例如,我們可以引入一個(gè)檢查性的PetClinicException,并告訴事務(wù)代理,在拋出該異常時(shí)執(zhí)行進(jìn)行回滾。
<prop key="load*">PROPAGATION_REQUIRED,readOnly,-PetClinicException</prop><prop key="store*">PROPAGATION_REQUIRED,-PetClinicException</prop>
還有一個(gè)類似的“提交規(guī)則”,指定了觸發(fā)提交的具體異常。
注意,上面所顯示的顯式查詢僅僅是用來(lái)訪問(wèn)Spring托管的bean的通用變體,它用于任何一種web資源中,比如servlet或filter。在使用Spring固有的web MVC框架構(gòu)建web層時(shí),這些bean可以被直接插入到web控制器中。其他的一些應(yīng)用框架也支持對(duì)Spring bean的便捷訪問(wèn),例如Struts、WebWork、JSF和Tapestry。詳細(xì)資料請(qǐng)參考Spring說(shuō)明文檔。
PlatformTransactionManager策略
Spring事務(wù)支持中的核心接口是org.springframework.transaction.PlatformTransactionManager。為了實(shí)際執(zhí)行事務(wù),Spring所有的事務(wù)劃分功能都通過(guò)傳遞適當(dāng)?shù)腡ransactionDefinition實(shí)例,委托給PlatformTransactionManager。盡管PlatformTransactionManager接口可以直接使用,應(yīng)用程序通常配置具體的事務(wù)管理器并使用聲明性事務(wù)來(lái)劃分事務(wù)。
Spring具有多種PlatformTransactionManager實(shí)現(xiàn),它們分為兩類:
PlatformTransactionManager抽象化的主要意義在于,應(yīng)用程序并不限定于某個(gè)特定的事務(wù)管理環(huán)境。相反,通過(guò)選擇PlatformTransactionManager接口的不同實(shí)現(xiàn)類,可以很容易地切換事務(wù)策略。這允許應(yīng)用程序代碼和聲明性事務(wù)劃分保持不變,不管應(yīng)用程序組件用在哪種環(huán)境中。
例如,應(yīng)用程序的一個(gè)基礎(chǔ)版本可能部署到Tomcat環(huán)境中,并與一個(gè)Oracle數(shù)據(jù)庫(kù)交互。它可以使用便捷的Spring事務(wù)劃分,選擇JDBC DataSourceTransactionManager作為其事務(wù)策略。Spring將劃分事務(wù),而且JDBC驅(qū)動(dòng)程序?qū)?zhí)行對(duì)應(yīng)的純JDBC事務(wù)。
而這個(gè)應(yīng)用程序的另外一個(gè)版本也許部署在WebLogic Server環(huán)境中,與兩個(gè)Oracle數(shù)據(jù)庫(kù)交互。但是,應(yīng)用程序代碼和事務(wù)劃分無(wú)需更改。唯一需要調(diào)整的是選擇JtaTransactionManager作為事務(wù)策略,讓Spring劃分事務(wù),而WebLogic Server的事務(wù)協(xié)調(diào)程序執(zhí)行事務(wù)。
JTA UserTransaction與JTA TransactionManager
讓我們來(lái)看一些Spring的JTA支持的詳細(xì)情況。雖然理解該機(jī)制是很有幫助的,但是通常不必為之擔(dān)心。對(duì)于像前面的小節(jié)所展示的簡(jiǎn)單用例,只需要一個(gè)標(biāo)準(zhǔn)的JtaTransactionManager定義,以及由J2EE服務(wù)器提供的支持XA規(guī)范的DataSource。
默認(rèn)的Spring JtaTransactionManager設(shè)置將從標(biāo)準(zhǔn)的JNDI位置獲取JTA的javax.transaction.UserTransaction對(duì)象,該JNDI位置由J2EE指定:java:comp/UserTransaction。對(duì)于大多數(shù)標(biāo)準(zhǔn)J2EE環(huán)境下的用例來(lái)說(shuō),它工作良好。
但是,默認(rèn)的JtaTransactionManager不能執(zhí)行事務(wù)掛起操作(即它不支持PROPAGATION_REQUIRES_NEW和PROPAGATION_NOT_SUPPORTED)。原因是標(biāo)準(zhǔn)的JTA UserTransaction接口不支持掛起或恢復(fù)事務(wù)的操作;它只支持開(kāi)始和完成新事務(wù)的操作。
為執(zhí)行事務(wù)掛起操作,還需要提供javax.transaction.TransactionManager實(shí)例,按照J(rèn)TA的規(guī)定,它提供標(biāo)準(zhǔn)的掛起和恢復(fù)方法。遺憾的是,J2EE沒(méi)有為JTA TransactionManager定義標(biāo)準(zhǔn)的JNDI位置!因此,必須使用特定于供應(yīng)商的(vendor-specific)查尋機(jī)制。
<bean id="transactionManager"class="org.springframework.transaction.jta.JtaTransactionManager"><property name="transactionManagerName"><value>vendorSpecificJndiLocation</value></property></bean>
實(shí)質(zhì)上,J2EE沒(méi)有考慮把JTA TransactionManager接口作為它的公開(kāi)API的一部分。JTA規(guī)范規(guī)定的TransactionManager接口原本是打算用于容器集成的。雖然這可以理解,但是為JTA TransactionManager定義標(biāo)準(zhǔn)的JNDI位置還是有重大意義的,尤其是對(duì)于輕量級(jí)容器(如Spring);然后,便可以以同樣的方式來(lái)定位任意的J2EE服務(wù)器的JTA TransactionManager。
不僅Spring的JtaTransactionManager將從對(duì)JTA TransactionManager的訪問(wèn)中獲益,而且O/R映射工具,比如Hibernate、Apache OJB和Kodo JDO也將從中受益,因?yàn)樗麄冃枰迷摻涌谠贘TA環(huán)境中執(zhí)行緩存同步--即在JTA事務(wù)完成時(shí)釋放緩存鎖。注冊(cè)事務(wù)同步的能力只能由JTA TransactionManager接口而不是UserTransaction句柄提供。因此,每個(gè)工具都需要實(shí)現(xiàn)自己特定于供應(yīng)商的TransactionManager查尋適配器。
為JTA TransactionManager定義標(biāo)準(zhǔn)的JNDI位置是許多基礎(chǔ)架構(gòu)軟件供應(yīng)商在J2EE方面的強(qiáng)烈愿望。如果J2EE 5.0規(guī)范的開(kāi)發(fā)團(tuán)隊(duì)能意識(shí)到該功能的重要性,那就太好了。幸運(yùn)的是,優(yōu)秀的J2EE服務(wù)器(如WebLogic Server)已經(jīng)考慮將其JTA TransactionManager作為公開(kāi)的API,包括特定于供應(yīng)商的擴(kuò)展!
結(jié)合了WebLogic JTA的Spring事務(wù)劃分
對(duì)于WebLogic Server,JTA TransactionManager的正式JNDI位置是javax.transaction.TransactionManager。在Spring的JtaTransactionManager中,該值可被指定為“transactionManagerName”。一般來(lái)說(shuō),這啟用了使用WebLogic JTA子系統(tǒng)的Spring驅(qū)動(dòng)的事務(wù)掛起,激活了對(duì)PROPAGATION_REQUIRES_NEW和PROPAGATION_NOT_SUPPORTED的支持。
<bean id="transactionManager"class="org.springframework.transaction.jta.JtaTransactionManager"><property name="transactionManagerName"><value>javax.transaction.TransactionManager</value></property></bean>
除了標(biāo)準(zhǔn)的JtaTransactionManager和它所支持的通用配置選項(xiàng),Spring還支持一種特殊的WebLogicJtaTransactionManager適配器,它直接利用WebLogic的JTA擴(kuò)展。
<bean id="transactionManager" class="org.springframework.transaction.jta.WebLogicJtaTransactionManager"/>
除了自動(dòng)檢測(cè)WebLogic的JTA TransactionManager,它還支持不屬于標(biāo)準(zhǔn)JTA的三個(gè)重要特性:
下面的圖像顯示的是WebLogic Server的事務(wù)監(jiān)視器,它按名稱列出了一組Spring驅(qū)動(dòng)的事務(wù):
圖2. WebLogic Server的事務(wù)監(jiān)視器(點(diǎn)擊圖片查看大圖)
Spring的WebLogicJtaTransactionManager實(shí)際上暴露了WebLogic Server的事務(wù)管理器針對(duì)基于Spring的應(yīng)用程序的全部功能。它使Spring事務(wù)劃分成為EJB CMT的一個(gè)極具吸引人的替代方案,而且它提供同級(jí)別的事務(wù)支持。
注意,只有實(shí)際需要掛起事務(wù)或使用WebLogic的JTA擴(kuò)展時(shí),才需要對(duì)特定于WebLogic的JTA進(jìn)行設(shè)置。對(duì)于標(biāo)準(zhǔn)的事務(wù)劃分(如PROPAGATION_REQUIRED或PROPAGATION_SUPPORTS),標(biāo)準(zhǔn)的JTA設(shè)置就足夠了。
Spring和EJB CMT
如上所述,針對(duì)POJO的Spring聲明性事務(wù)劃分可以看作傳統(tǒng)的EJB CMT的替代方案。但是,Spring和EJB并不是互相排斥的。Spring應(yīng)用程序上下文也可以作為EJB外觀的后端,管理數(shù)據(jù)訪問(wèn)對(duì)象(DAO)和其它的細(xì)粒度業(yè)務(wù)對(duì)象。
在EJB場(chǎng)景中,事務(wù)由EJB CMT驅(qū)動(dòng)。Spring的數(shù)據(jù)訪問(wèn)支持會(huì)自動(dòng)地檢測(cè)這樣的環(huán)境并進(jìn)行相應(yīng)的調(diào)整。例如,Spring的Hibernate支持將為其隱式資源管理提供EJB驅(qū)動(dòng)的事務(wù),就像它提供Spring驅(qū)動(dòng)的事務(wù)一樣。它甚至提供相同的語(yǔ)義,而不需要對(duì)DAO代碼做任何修改。
Spring有效地將DAO實(shí)現(xiàn)從實(shí)際的運(yùn)行時(shí)環(huán)境中分離出來(lái)。DAO可以參與到Spring事務(wù)(以哪個(gè)事務(wù)策略作為后端都可以)和EJB CMT事務(wù)中。這不僅支持其它環(huán)境中的重用,還支持在J2EE容器之外的測(cè)試中直接使用。
結(jié)束語(yǔ)
Spring Framework為J2EE和非J2EE環(huán)境提供了完善的事務(wù)劃分功能,具體來(lái)說(shuō)就是為純Java目標(biāo)對(duì)象提供聲明性事務(wù)。這允許在沒(méi)有EJB的情況下,以一種靈活和非入侵的方式便捷地進(jìn)行事務(wù)劃分。與EJB相比,這些事務(wù)POJO應(yīng)用程序?qū)ο罂梢栽贘2EE容器之外輕松地測(cè)試或重用。
Spring提供了多種開(kāi)箱即用的事務(wù)策略,比如JtaTransactionManager和JDBC DataSourceTransactionManager,前者委托給J2EE服務(wù)器的事務(wù)協(xié)調(diào)程序,后者則針對(duì)單個(gè)JDBC DataSource(即單個(gè)的目標(biāo)數(shù)據(jù)庫(kù))執(zhí)行事務(wù)。通過(guò)對(duì)后端配置進(jìn)行簡(jiǎn)單的更改,Spring就能夠輕松地調(diào)整事務(wù)策略適應(yīng)另一個(gè)環(huán)境。
除了標(biāo)準(zhǔn)的JTA支持,Spring還提供與WebLogic Server的JTA擴(kuò)展的完善集成,支持一些高級(jí)特性(如事務(wù)監(jiān)控和按事務(wù)的隔離級(jí)別)。通過(guò)這種專門(mén)的WebLogic Server支持,WebLogic的事務(wù)管理器的全部功能都可應(yīng)用于基于Spring的應(yīng)用程序。
Spring事務(wù)劃分是對(duì)EJB CMT的極具吸引人的替代方案,特別是與基于POJO的輕量級(jí)架構(gòu)結(jié)合后。在一個(gè)聲明性事務(wù)是選擇Local Stateless Session Bean(局部無(wú)狀態(tài)會(huì)話Bean)的惟一原因的場(chǎng)景中,基于Spring的POJO服務(wù)模型是一個(gè)可行的選擇,它提供更高水平的靈活性、可測(cè)試性和重用。
參考資料
關(guān)于作者
Juergen Hoeller是Spring Framework項(xiàng)目的締造者之一。
聯(lián)系客服