?
Ce document utilise Manuel du site Web PHP chinois Libérer
大多數(shù)Spring用戶選擇聲明式事務(wù)管理。這是對應(yīng)用代碼影響最小的選擇,因此也最符合 非侵入式 輕量級容器的理念。
Spring的聲明式事務(wù)管理是通過Spring AOP實(shí)現(xiàn)的,因?yàn)槭聞?wù)方面的代碼與Spring綁定并以一種樣板式風(fēng)格使用, 不過盡管如此,你一般并不需要理解AOP概念就可以有效地使用Spirng的聲明式事務(wù)管理。
從考慮EJB CMT和Spring聲明式事務(wù)管理的相似以及不同之處出發(fā)是很有益的。它們的基本方法是相似的:
都可以指定事務(wù)管理到單獨(dú)的方法;如果需要可以在事務(wù)上下文調(diào)用 setRollbackOnly()
方法。不同之處在于:
不像EJB CMT綁定在JTA上,Spring聲明式事務(wù)管理可以在任何環(huán)境下使用。只需更改配置文件, 它就可以和JDBC、JDO、Hibernate或其他的事務(wù)機(jī)制一起工作。
Spring的聲明式事務(wù)管理可以被應(yīng)用到任何類(以及那個類的實(shí)例)上,不僅僅是像EJB那樣的特殊類。
Spring提供了聲明式的 回滾規(guī)則:EJB沒有對應(yīng)的特性,我們將在下面討論?;貪L可以聲明式的控制,不僅僅是編程式的。
Spring允許你通過AOP定制事務(wù)行為。例如,如果需要,你可以在事務(wù)回滾中插入定制的行為。
你也可以增加任意的通知,就象事務(wù)通知一樣。使用EJB CMT,除了使用setRollbackOnly()
,你沒有辦法能夠影響容器的事務(wù)管理。
Spring不提供高端應(yīng)用服務(wù)器提供的跨越遠(yuǎn)程調(diào)用的事務(wù)上下文傳播。如果你需要這些特性,我們推薦你使用EJB。 然而,不要輕易使用這些特性。因?yàn)橥ǔN覀儾⒉幌M聞?wù)跨越遠(yuǎn)程調(diào)用。
回滾規(guī)則的概念比較重要:它使我們能夠指定什么樣的異常(和throwable)將導(dǎo)致自動回滾。
我們在配置文件中聲明式地指定,無須在Java代碼中。同時,我們?nèi)耘f可以通過調(diào)用 TransactionStatus
的
setRollbackOnly()
方法編程式地回滾當(dāng)前事務(wù)。通常,我們定義一條規(guī)則,
聲明 MyApplicationException
必須總是導(dǎo)致事務(wù)回滾。
這種方式帶來了顯著的好處,它使你的業(yè)務(wù)對象不必依賴于事務(wù)設(shè)施。典型的例子是你不必在代碼中導(dǎo)入Spring API,事務(wù)等。
對EJB來說,默認(rèn)的行為是EJB容器在遇到 系統(tǒng)異常(通常指運(yùn)行時異常)時自動回滾當(dāng)前事務(wù)。
EJB CMT遇到 應(yīng)用異常(例如,除了 java.rmi.RemoteException
外別的checked exception)時并不會自動回滾。
默認(rèn)式Spring處理聲明式事務(wù)管理的規(guī)則遵守EJB習(xí)慣(只在遇到unchecked exceptions時自動回滾),但通常定制這條規(guī)則會更有用。
本節(jié)的目的是消除與使用聲明式事務(wù)管理有關(guān)的神秘性。簡單點(diǎn)兒總是好的,這份參考文檔只是告訴你給你的類加上@Transactional
注解,在配置文件中添加('<tx:annotation-driven/>'
)行,然后期望你理解整個過程是怎么工作的。此節(jié)講述Spring的聲明式事務(wù)管理內(nèi)部的工作機(jī)制,以幫助你在面對事務(wù)相關(guān)的問題時不至于誤入迷途,回朔到上游平靜的水域。
在理解Spring的聲明式事務(wù)管理方面最重要的概念是:Spring的事務(wù)管理是通過AOP代理實(shí)現(xiàn)的。
其中的事務(wù)通知由元數(shù)據(jù)(目前基于XML或注解)驅(qū)動。
代理對象與事務(wù)元數(shù)據(jù)結(jié)合產(chǎn)生了一個AOP代理,它使用一個PlatformTransactionManager
實(shí)現(xiàn)品配合TransactionInterceptor
,在方法調(diào)用前后實(shí)施事務(wù)。
盡管使用Spring聲明式事務(wù)管理不需要AOP(尤其是Spring AOP)的知識,但了解這些是很有幫助的。你可以在 第?6?章 使用Spring進(jìn)行面向切面編程(AOP) 章找到關(guān)于Spring AOP的全部內(nèi)容。
概念上來說,在事務(wù)代理上調(diào)用方法的工作過程看起來像這樣:
請看下面的接口和它的實(shí)現(xiàn)。這個例子的意圖是介紹概念,使用 Foo
和 Bar
這樣的名字只是為了讓你關(guān)注于事務(wù)的用法,而不是領(lǐng)域模型。
// 我們想做成事務(wù)性的服務(wù)接口
package x.y.service;
public interface FooService {
Foo getFoo(String fooName);
Foo getFoo(String fooName, String barName);
void insertFoo(Foo foo);
void updateFoo(Foo foo);
}
// 上述接口的一個實(shí)現(xiàn)
package x.y.service;
public class DefaultFooService implements FooService {
public Foo getFoo(String fooName) {
throw new UnsupportedOperationException();
}
public Foo getFoo(String fooName, String barName) {
throw new UnsupportedOperationException();
}
public void insertFoo(Foo foo) {
throw new UnsupportedOperationException();
}
public void updateFoo(Foo foo) {
throw new UnsupportedOperationException();
}
}
(對該例的目的來說,上例中實(shí)現(xiàn)類(DefaultFooService
)的每個方法在其方法體中拋出
UnsupportedOperationException
的做法是恰當(dāng)?shù)?,我們可以看到,事?wù)被創(chuàng)建出來,
響應(yīng) UnsupportedOperationException
的拋出,然后回滾。)
我們假定,FooService
的前兩個方法(getFoo(String)
和getFoo(String, String)
)必須執(zhí)行在只讀事務(wù)上下文中,其他的方法(insertFoo(Foo)
和
updateFoo(Foo)
)必須執(zhí)行在可讀寫事務(wù)上下文中。不要想著一次理解下面的配置,所有內(nèi)容都會在后面的章節(jié)詳細(xì)討論。
<!-- from the file'context.xml'
--> <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd"> <!-- this is the service object that we want to make transactional --> <bean id="fooService" class="x.y.service.DefaultFooService"/> <!-- the transactional advice (what 'happens'; see the<aop:advisor/>
bean below) --> <tx:advice id="txAdvice" transaction-manager="txManager"> <!-- the transactional semantics... --> <tx:attributes> <!-- all methods starting with'get'
are read-only --> <tx:method name="get*" read-only="true"/> <!-- other methods use the default transaction settings (see below) --> <tx:method name="*"/> </tx:attributes> </tx:advice> <!-- ensure that the above transactional advice runs for any execution of an operation defined by theFooService
interface --> <aop:config> <aop:pointcut id="fooServiceOperation" expression="execution(* x.y.service.FooService.*(..))"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceOperation"/> </aop:config> <!-- don't forget theDataSource
--> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/> <property name="url" value="jdbc:oracle:thin:@rj-t42:1521:elvis"/> <property name="username" value="scott"/> <property name="password" value="tiger"/> </bean> <!-- similarly, don't forget thePlatformTransactionManager
--> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <!-- other<bean/>
definitions here --> </beans>
我們來分析一下上面的配置。我們要把一個服務(wù)對象('fooService'
bean)做成事務(wù)性的。
我們想施加的事務(wù)語義封裝在<tx:advice/>
定義中。<tx:advice/>
“把所有以 'get'
開頭的方法看做執(zhí)行在只讀事務(wù)上下文中,
其余的方法執(zhí)行在默認(rèn)語義的事務(wù)上下文中”。
其中的 'transaction-manager'
屬性被設(shè)置為一個指向
PlatformTransactionManager
bean的名字(這里指 'txManager'
),
該bean將會真正管理事務(wù)。
事實(shí)上,如果 PlatformTransactionManager
bean的名字是
'transactionManager'
的話,你的事務(wù)通知(<tx:advice/>
)中的
'transaction-manager'
屬性可以忽略。否則你則需要像上例那樣明確指定。
配置中最后一段是 <aop:config/>
的定義,
它確保由 'txAdvice'
bean定義的事務(wù)通知在應(yīng)用中合適的點(diǎn)被執(zhí)行。
首先我們定義了 一個切面,它匹配 FooService
接口定義的所有操作,
我們把該切面叫做 'fooServiceOperation'
。然后我們用一個通知器(advisor)把這個切面與 'txAdvice'
綁定在一起,
表示當(dāng) 'fooServiceOperation'
執(zhí)行時,'txAdvice'
定義的通知邏輯將被執(zhí)行。
<aop:pointcut/>
元素定義是AspectJ的切面表示法,可參考Spring 2.0 第?6?章 使用Spring進(jìn)行面向切面編程(AOP)
章獲得更詳細(xì)的內(nèi)容。
一個普遍性的需求是讓整個服務(wù)層成為事務(wù)性的。滿足該需求的最好方式是讓切面表達(dá)式匹配服務(wù)層的所有操作方法。例如:
<aop:config> <aop:pointcut id="fooServiceMethods" expression="execution(* x.y.service.*.*(..))"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceMethods"/> </aop:config>
(這個例子中假定你所有的服務(wù)接口定義在 'x.y.service'
包中。你同樣可以參考 第?6?章 使用Spring進(jìn)行面向切面編程(AOP)
章獲得更詳細(xì)內(nèi)容。)
現(xiàn)在,既然我們已經(jīng)分析了整個配置,你可能會問了,“好吧,但是所有這些配置做了什么?”。
上面的配置將為'fooService'
bean創(chuàng)建一個代理對象,這個代理對象被裝配了事務(wù)通知,所以當(dāng)它的相應(yīng)方法被調(diào)用時,一個事務(wù)將被啟動、掛起、被標(biāo)記為只讀,或者其它(根據(jù)該方法所配置的事務(wù)語義)。我們來看看下面的例子,測試一下上面的配置。
public final class Boot { public static void main(final String[] args) throws Exception { ApplicationContext ctx = new ClassPathXmlApplicationContext("context.xml", Boot.class); FooService fooService = (FooService) ctx.getBean("fooService"); fooService.insertFoo (new Foo()); } }
運(yùn)行上面程序的輸出結(jié)果看起來像這樣(注意為了清楚起見,Log4J的消息和從 DefaultFooService
的 insertFoo(..)
方法拋出的 UnsupportedOperationException
異常堆棧信息被省略了)。
<!-- Spring容器開始啟動... --> [AspectJInvocationContextExposingAdvisorAutoProxyCreator] - Creating implicit proxy for bean 'fooService' with 0 common interceptors and 1 specific interceptors <!-- theDefaultFooService
is actually proxied --> [JdkDynamicAopProxy] - Creating JDK dynamic proxy for [x.y.service.DefaultFooService] <!-- ... theinsertFoo(..)
method is now being invoked on the proxy --> [TransactionInterceptor] - Getting transaction for x.y.service.FooService.insertFoo <!-- the transactional advice kicks in here... --> [DataSourceTransactionManager] - Creating new transaction with name [x.y.service.FooService.insertFoo] [DataSourceTransactionManager] - Acquired Connection [org.apache.commons.dbcp.PoolableConnection@a53de4] for JDBC transaction <!-- theinsertFoo(..)
method fromDefaultFooService
throws an exception... --> [RuleBasedTransactionAttribute] - Applying rules to determine whether transaction should rollback on java.lang.UnsupportedOperationException [TransactionInterceptor] - Invoking rollback for transaction on x.y.service.FooService.insertFoo due to throwable [java.lang.UnsupportedOperationException] <!-- and the transaction is rolled back (by default,RuntimeException
instances cause rollback) --> [DataSourceTransactionManager] - Rolling back JDBC transaction on Connection [org.apache.commons.dbcp.PoolableConnection@a53de4] [DataSourceTransactionManager] - Releasing JDBC Connection after transaction [DataSourceUtils] - Returning JDBC Connection to DataSource Exception in thread "main" java.lang.UnsupportedOperationException at x.y.service.DefaultFooService.insertFoo(DefaultFooService.java:14) <!-- AOP infrastructure stack trace elements removed for clarity --> at $Proxy0.insertFoo(Unknown Source) at Boot.main(Boot.java:11)
在前面的章節(jié)里,概述了如何在你的應(yīng)用中用聲明的風(fēng)格為類(特別是服務(wù)層的類)指定事務(wù)配置。 這一章將描述如何使用一個簡單的聲明式配置來控制事務(wù)的回滾。
我們推薦做法是在Spring框架的事務(wù)架構(gòu)里指出當(dāng)context的事務(wù)里的代碼拋出
Exception
時事務(wù)進(jìn)行回滾。Spring框架的事務(wù)基礎(chǔ)架構(gòu)代碼將從調(diào)用的堆棧里捕獲到任何未處理的
Exception
,并將標(biāo)識事務(wù)將回滾。
然而,請注意Spring框架的事務(wù)基礎(chǔ)架構(gòu)代碼將默認(rèn)地 只 在拋出運(yùn)行時和unchecked exceptions時才標(biāo)識事務(wù)回滾。
也就是說,當(dāng)拋出一個 RuntimeException
或其子類例的實(shí)例時。(Errors
也一樣 - 默認(rèn)地 - 標(biāo)識事務(wù)回滾。)從事務(wù)方法中拋出的Checked exceptions將 不 被標(biāo)識進(jìn)行事務(wù)回滾。
可以配置哪些 Exception
類型將被標(biāo)識進(jìn)行事務(wù)回滾。
下面的XML配置片斷里示范了如何配置一個用于回滾的checked、應(yīng)用程序特定的 Exception
類型。
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="get*" read-only="true" rollback-for="NoProductInStockException"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
有時候你不想在異常拋出的時候回滾事務(wù),就可以使用“不回滾規(guī)則”。
在下面的例子中,我們告訴Spring 框架即使遇到?jīng)]有經(jīng)過處理的InstrumentNotFoundException
異常,也不要回滾事務(wù)。
<tx:advice id="txAdvice">
<tx:attributes>
<tx:method name="updateStock" no-rollback-for="InstrumentNotFoundException"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
當(dāng)Spring框架捕獲到一個異常后會檢查配置回滾規(guī)則來決定是不是要回滾事務(wù),這時候會遵循最匹配的規(guī)則。
所以在下面這種配置中,除了InstrumentNotFoundException
這種類型的異常不會導(dǎo)致事務(wù)回滾以外,其他任何類型的異常都會。
<tx:advice id="txAdvice"> <tx:attributes> <tx:method name="*" rollback-for="Throwable" no-rollback-for="InstrumentNotFoundException"/> </tx:attributes> </tx:advice>
第二種方法是通過 編程式 方式來指定回滾事務(wù)。 雖然寫法非常的簡單,但是這個方法是高侵入性的,并且使你的代碼與Spring框架的事務(wù)架構(gòu)高度耦合。 下面的代碼片斷里示范了Spring框架管理事務(wù)的編程式回滾:
public void resolvePosition() { try { // some business logic... } catch (NoProductInStockException ex) { // trigger rollback programmatically TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); } }
強(qiáng)烈推薦你盡可能地使用聲明式事務(wù)回滾方法。 編程式方法的回滾對你來說是可見,如果你需要它你就可以使用,但是使用它就直接違反了在你的應(yīng)用中使用一個純基于POJO的模型。
現(xiàn)在讓我們考慮一下這樣的場景,假設(shè)你有許多服務(wù)對象,你想為他們分別設(shè)置 完全不同 的事務(wù)語義。
在Spring中,你可以通過分別定義特定的 <aop:advisor/>
元素,
讓每個advisor采用不同的 'pointcut'
和 'advice-ref'
屬性,來達(dá)到目的。
讓我們假定你所有的服務(wù)層類定義在以 'x.y.service'
為根的包內(nèi)。
為了讓service包(或子包)下所有名字以 'Service'
結(jié)尾的類的對象擁有默認(rèn)的事務(wù)語義,你可以做如下的配置:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
<aop:config>
<aop:pointcut id="serviceOperation"
expression="execution(* x.y.service..*Service.*(..))"/>
<aop:advisor pointcut-ref="serviceOperation" advice-ref="txAdvice"/>
</aop:config>
<!-- these two beans will be transactional... -->
<bean id="fooService" class="x.y.service.DefaultFooService"/>
<bean id="barService" class="x.y.service.extras.SimpleBarService"/>
<!-- ... and these two beans won't -->
<bean id="anotherService" class="org.xyz.SomeService"/> <!-- (not in the right package) -->
<bean id="barManager" class="x.y.service.SimpleBarManager"/> <!-- (doesn't end in 'Service') -->
<tx:advice id="txAdvice">
<tx:attributes>
<tx:method name="get*" read-only="true"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<!-- other transaction infrastructure beans such as a PlatformTransactionManager
omitted... -->
</beans>
下面的配置示例演示了兩個擁有完全不同的事務(wù)配置的bean。
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd"> <aop:config> <aop:pointcut id="defaultServiceOperation" expression="execution(* x.y.service.*Service.*(..))"/> <aop:pointcut id="noTxServiceOperation" expression="execution(* x.y.service.ddl.DefaultDdlManager.*(..))"/> <aop:advisor pointcut-ref="defaultServiceOperation" advice-ref="defaultTxAdvice"/> <aop:advisor pointcut-ref="noTxServiceOperation" advice-ref="noTxAdvice"/> </aop:config> <!-- this bean will be transactional (see the'defaultServiceOperation'
pointcut) --> <bean id="fooService" class="x.y.service.DefaultFooService"/> <!-- this bean will also be transactional, but with totally different transactional settings --> <bean id="anotherFooService" class="x.y.service.ddl.DefaultDdlManager"/> <tx:advice id="defaultTxAdvice"> <tx:attributes> <tx:method name="get*" read-only="true"/> <tx:method name="*"/> </tx:attributes> </tx:advice> <tx:advice id="noTxAdvice"> <tx:attributes> <tx:method name="*" propagation="NEVER"/> </tx:attributes> </tx:advice> <!-- other transaction infrastructure beans such as aPlatformTransactionManager
omitted... --> </beans>
這一節(jié)里將描述通過 <tx:advice/>
標(biāo)簽來指定不同的事務(wù)性設(shè)置。默認(rèn)的 <tx:advice/>
設(shè)置如下:
事務(wù)傳播設(shè)置 是 REQUIRED
隔離級別是DEFAULT
事務(wù)是 讀/寫
事務(wù)超時默認(rèn)是依賴于事務(wù)系統(tǒng)的,或者事務(wù)超時沒有被支持。
任何 RuntimeException
將觸發(fā)事務(wù)回滾,但是任何 checked Exception
將不觸發(fā)事務(wù)回滾
這些默認(rèn)的設(shè)置當(dāng)然也是可以被改變的。
<tx:advice/>
和 <tx:attributes/>
標(biāo)簽里的 <tx:method/>
各種屬性設(shè)置總結(jié)如下:
表?9.1.?<tx:method/>
有關(guān)的設(shè)置
屬性 | 是否需要? | 默認(rèn)值 | 描述 |
---|---|---|---|
name |
是 | ? |
與事務(wù)屬性關(guān)聯(lián)的方法名。通配符(*)可以用來指定一批關(guān)聯(lián)到相同的事務(wù)屬性的方法。
如: |
propagation |
不 | REQUIRED | 事務(wù)傳播行為 |
isolation |
不 | DEFAULT | 事務(wù)隔離級別 |
timeout |
不 | -1 | 事務(wù)超時的時間(以秒為單位) |
read-only |
不 | false | 事務(wù)是否只讀? |
rollback-for |
不 | ? |
將被觸發(fā)進(jìn)行回滾的 |
no-rollback-for |
不 | ? |
不 被觸發(fā)進(jìn)行回滾的 |
在寫代碼的時候,不可能對事務(wù)的名字有個很清晰的認(rèn)識,這里的名字是指會在事務(wù)監(jiān)視器(比如WebLogic的事務(wù)管理器)或者日志輸出中顯示的名字,
對于聲明式的事務(wù)設(shè)置,事務(wù)名字總是包含完整包名的類名加上"."和方法名,比如 'com.foo.BusinessService.handlePayment'
.
@Transactional
注解及其支持類所提供的功能最低要求使用Java 5(Tiger)。
除了基于XML文件的聲明式事務(wù)配置外,你也可以采用基于注解式的事務(wù)配置方法。直接在Java源代碼中聲明事務(wù)語義的做法讓事務(wù)聲明和將受其影響的代碼距離更近了,而且一般來說不會有不恰當(dāng)?shù)鸟詈系娘L(fēng)險,因?yàn)?,使用事?wù)性的代碼幾乎總是被部署在事務(wù)環(huán)境中。
下面的例子很好地演示了 @Transactional
注解的易用性,隨后解釋其中的細(xì)節(jié)。先看看其中的類定義:
// the service class that we want to make transactional @Transactional public class DefaultFooService implements FooService { Foo getFoo(String fooName); Foo getFoo(String fooName, String barName); void insertFoo(Foo foo); void updateFoo(Foo foo); }
當(dāng)上述的POJO定義在Spring IoC容器里時,上述bean實(shí)例僅僅通過一 行xml配置就可以使它具有事務(wù)性的。如下:
<!-- from the file'context.xml'
--> <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd"> <!-- this is the service object that we want to make transactional --> <bean id="fooService" class="x.y.service.DefaultFooService"/> <!-- enable the configuration of transactional behavior based on annotations --> <tx:annotation-driven transaction-manager="txManager"/> <!-- aPlatformTransactionManager
is still required --> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!-- (this dependency is defined somewhere else) --> <property name="dataSource" ref="dataSource"/> </bean> <!-- other<bean/>
definitions here --> </beans>
實(shí)際上,如果你用 'transactionManager'
來定義 PlatformTransactionManager
bean的名字的話,你就可以忽略 <tx:annotation-driven/>
標(biāo)簽里的 'transaction-manager'
屬性。
如果 PlatformTransactionManager
bean你要通過其它名稱來注入的話,你必須用 'transaction-manager'
屬性來指定它,如上所示。
@Transactional
注解可以被應(yīng)用于接口定義和接口方法、類定義和類的 public 方法上。
然而,請注意只是使用 @Transactional
注解并不會啟用事務(wù)行為,
它僅僅 是一種元數(shù)據(jù),能夠被可以識別 @Transactional
注解和上述的配置適當(dāng)?shù)木哂惺聞?wù)行為的beans所使用。上面的例子中,其實(shí)正是 <tx:annotation-driven/>
元素的出現(xiàn)
開啟 了事務(wù)行為。
Spring團(tuán)隊的建議是你只在具體的類上使用 @Transactional
注解,
而不要注解在接口上。你當(dāng)然可以在接口(或接口方法)上使用 @Transactional
注解,
但是這只有在你使用基于接口的代理時它才會生效。因?yàn)樽⒔馐?不能繼承 的,
這就意味著如果你正在使用基于類的代理時,事務(wù)的設(shè)置將不能被基于類的代理所識別,而且對象也不會被事務(wù)代理所包裝
(這是很糟糕的)。
因此,請接受Spring團(tuán)隊的建議,在具體的類(包括該類的方法)上使用 @Transactional
注解。
注意:在代理模式下(默認(rèn)的情況),只有從代理傳過來的‘外部’方法調(diào)用才會被攔截。
這就意味著‘自我調(diào)用’是不會觸發(fā)事務(wù)的,比如說,一個在目標(biāo)對象中調(diào)用目標(biāo)對象其他方法的方法是不會觸發(fā)一個事務(wù)的,即使這個方法被標(biāo)記為
@Transactional
!
如果你期望‘自我調(diào)用’被事務(wù)覆蓋到,可以考慮使用AspectJ 模式(如下所示)。在這種情況下,一開始就沒有任何代理的存在;
為了把@Transactional
的方法變成運(yùn)行時的行為,目標(biāo)類會被‘編織’起來(比如修改它的字節(jié)碼)。
表?9.2.?<tx:annotation-driven/>
設(shè)置
屬性 | 默認(rèn)值 | 描述 |
---|---|---|
transaction-manager |
transactionManager | 使用的事務(wù)管理器的名字。只有像在上面的例子那樣,事務(wù)管理器不是 |
mode |
proxy | 默認(rèn)的模式“proxy”會用Spring的AOP框架來代理注解過的bean(就像在前面討論過的那樣, 下面代理的語義只對通過代理傳遞過來的方法調(diào)用起效)。 另一種可行的模式"aspectj"會使用Spring的AspectJ事務(wù)切面來編織類(通過修改目標(biāo)對象的字節(jié)碼應(yīng)用到任何方法調(diào)用上)。 AspectJ織入需要在classpath中有spring-aspects.jar這個文件,并且啟用裝載時織入 (或者編譯時織入)。 (關(guān)于如何設(shè)置裝載時編織的詳情請參見 第?6.8.4.5?節(jié) “Spring配置” ) |
proxy-target-class |
false | 只對代理模式有效。決定為那些使用了 |
order |
Ordered.LOWEST_PRECEDENCE | 定義事務(wù)通知的順序會作用到使用 |
在<tx:annotation-driven/>
元素上的"proxy-target-class
" 屬性
控制了有什么類型的事務(wù)性代理會為使用@Transactional
來注解的類創(chuàng)建代理。
如果"proxy-target-class
" 屬性被設(shè)為"true
",那么基于類的代理就會被創(chuàng)建。
如果"proxy-target-class
" 屬性被設(shè)為"false
"
或者沒設(shè),那么會創(chuàng)建基于接口的標(biāo)準(zhǔn)JDK代理。(關(guān)于不同代理類型的解釋請參見 第?6.6?節(jié) “代理機(jī)制”)
在多數(shù)情形下,方法的事務(wù)設(shè)置將被優(yōu)先執(zhí)行。在下列情況下,例如:
DefaultFooService
類在類的級別上被注解為只讀事務(wù),但是,這個類中的 updateFoo(Foo)
方法的 @Transactional
注解的事務(wù)設(shè)置將優(yōu)先于類級別注解的事務(wù)設(shè)置。
@Transactional(readOnly = true) public class DefaultFooService implements FooService { public Foo getFoo(String fooName) { // do something } // these settings have precedence for this method @Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW) public void updateFoo(Foo foo) { // do something } }
@Transactional
注解是用來指定接口、類或方法必須擁有事務(wù)語義的元數(shù)據(jù)。
如:“當(dāng)一個方法開始調(diào)用時就開啟一個新的只讀事務(wù),并停止掉任何現(xiàn)存的事務(wù)”。
默認(rèn)的 @Transactional
設(shè)置如下:
事務(wù)傳播設(shè)置是 PROPAGATION_REQUIRED
事務(wù)隔離級別是 ISOLATION_DEFAULT
事務(wù)是 讀/寫
事務(wù)超時默認(rèn)是依賴于事務(wù)系統(tǒng)的,或者事務(wù)超時沒有被支持。
任何 RuntimeException
將觸發(fā)事務(wù)回滾,但是任何 checked Exception
將不觸發(fā)事務(wù)回滾
這些默認(rèn)的設(shè)置當(dāng)然也是可以被改變的。
@Transactional
注解的各種屬性設(shè)置總結(jié)如下:
表?9.3.?@Transactional
注解的屬性
屬性 | 類型 | 描述 |
---|---|---|
propagation
|
枚舉型:Propagation
|
可選的傳播性設(shè)置 |
isolation |
枚舉型:Isolation
|
可選的隔離性級別(默認(rèn)值:ISOLATION_DEFAULT ) |
readOnly |
布爾型 | 讀寫型事務(wù) vs. 只讀型事務(wù) |
timeout |
int型(以秒為單位) | 事務(wù)超時 |
rollbackFor |
一組 Class 類的實(shí)例,必須是Throwable 的子類 |
一組異常類,遇到時 必須 進(jìn)行回滾。默認(rèn)情況下checked exceptions不進(jìn)行回滾,僅unchecked exceptions(即RuntimeException 的子類)才進(jìn)行事務(wù)回滾。 |
rollbackForClassname |
一組 Class 類的名字,必須是Throwable 的子類 |
一組異常類名,遇到時 必須 進(jìn)行回滾 |
noRollbackFor |
一組 Class 類的實(shí)例,必須是Throwable 的子類 |
一組異常類,遇到時 必須不 回滾。 |
noRollbackForClassname |
一組 Class 類的名字,必須是Throwable 的子類 |
一組異常類,遇到時 必須不 回滾 |
在寫代碼的時候,不可能對事務(wù)的名字有個很清晰的認(rèn)識,這里的名字是指會在事務(wù)監(jiān)視器(比如WebLogic的事務(wù)管理器)或者日志輸出中顯示的名字,
對于聲明式的事務(wù)設(shè)置,事務(wù)名字總是全限定名+"."+事務(wù)通知的類的方法名。比如BusinessService
類的handlePayment(..)
方法啟動了一個事務(wù),事務(wù)的名稱是:
com.foo.BusinessService.handlePayment
請注意這部分的Spring參考文檔不是 事務(wù)傳播的介紹, 而是詳細(xì)介紹了在Spring中與事務(wù)傳播相關(guān)的一些語義。
在由Spring管理的事務(wù)中,請記住 物理 和 邏輯 事務(wù)存在的差異, 以及傳播設(shè)置是如何影響到這些差異的。
PROPAGATION_REQUIRED
當(dāng)事務(wù)傳播被設(shè)置PROPAGATION_REQUIRED
的時候,
會為每一個被應(yīng)用到的方法創(chuàng)建一個邏輯事務(wù)作用域。
每一個這樣的邏輯事務(wù)作用域都可以自主地決定rollback-only狀態(tài),當(dāng)這樣的邏輯事務(wù)作用域被外部的一個邏輯事務(wù)作用域所包含的時候,
他們在邏輯上是獨(dú)立的。當(dāng)然了,對于正常的 PROPAGATION_REQUIRED
設(shè)置來說,他會被映射到相同的物理事務(wù)上。
所以一個標(biāo)記有rollback-only的內(nèi)部邏輯事務(wù)作用域的確會影響到外部的邏輯事務(wù)作用域(就像你所預(yù)料的那樣)。
然而,當(dāng)內(nèi)部的事務(wù)作用域標(biāo)記為rollback-only,同時外部的事務(wù)作用域并沒有決定要回滾,
這樣的回滾是意料不到的(靜悄悄地由內(nèi)部事務(wù)作用域觸發(fā)的):
一個對應(yīng)的UnexpectedRollbackException
異常會在這個時候被拋出。這是 可以預(yù)料到的行為,
只有這樣,這個事務(wù)的調(diào)用者才不會被誤導(dǎo),在事務(wù)沒有提交的情況下誤以為事務(wù)已經(jīng)提交。所以如果內(nèi)部的事務(wù)(外部的調(diào)用者并不知情)標(biāo)記該事務(wù)為
rollback-only,而外部的調(diào)用者卻依舊在不知情的情況下提交后,它需要收到一個 UnexpectedRollbackException
異常來清楚的了解事務(wù)并沒有提交而是發(fā)生了回滾。
PROPAGATION_REQUIRES_NEW
PROPAGATION_REQUIRES_NEW
,與之前相反,為每一個相關(guān)的事務(wù)作用域使用了一個完全
獨(dú)立的事務(wù)。在這種情況下,物理事務(wù)也將是不同的,因此外部事務(wù)可以不受內(nèi)部事務(wù)回滾狀態(tài)的影響?yīng)毩⑻峤换蛘呋貪L。
考慮一下這樣的情況,如果你希望 同時執(zhí)行事務(wù)性通知(advice)和一些基本的剖析(profiling)通知。
那么,在<tx:annotation-driven/>
環(huán)境中該怎么做?
我們調(diào)用 updateFoo(Foo)
方法時希望這樣:
配置的剖析切面(profiling aspect)開始啟動,
然后進(jìn)入事務(wù)通知(根據(jù)配置創(chuàng)建一個新事務(wù)或加入一個已經(jīng)存在的事務(wù)),
然后執(zhí)行原始對象的方法,
然后事務(wù)提交(我們假定這里一切正常),
最后剖析切面報告整個事務(wù)方法執(zhí)行過程花了多少時間。
這章不會專門講述AOP的所有細(xì)節(jié)(除了應(yīng)用于事務(wù)方面的之外)。 請參考 第?6?章 使用Spring進(jìn)行面向切面編程(AOP) 章節(jié)以獲得對各種AOP配置及其一般概念的詳細(xì)敘述。
這里有一份簡單的剖析切面(profiling aspect)的代碼。
(請注意,通知的順序是由 Ordered
接口來控制的。
要想了解更多細(xì)節(jié),請參考 第?6.2.4.7?節(jié) “通知順序” 節(jié)。)
package x.y; import org.aspectj.lang.ProceedingJoinPoint; import org.springframework.util.StopWatch; import org.springframework.core.Ordered; public class SimpleProfiler implements Ordered { private int order; // allows us to control the ordering of advice public int getOrder() { return this.order; } public void setOrder(int order) { this.order = order; } // this method is the around advice public Object profile(ProceedingJoinPoint call) throws Throwable { Object returnValue; StopWatch clock = new StopWatch(getClass().getName()); try { clock.start(call.toShortString()); returnValue = call.proceed(); } finally { clock.stop(); System.out.println(clock.prettyPrint()); } return returnValue; } }
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd"> <bean id="fooService" class="x.y.service.DefaultFooService"/> <!-- this is the aspect --> <bean id="profiler" class="x.y.SimpleProfiler"> <!-- execute before the transactional advice (hence the lower order number) --> <property name="order" value="1"/> </bean> <tx:annotation-driven transaction-manager="txManager" order="200"/> <aop:config> <!-- this advice will execute around the transactional advice --> <aop:aspect id="profilingAspect" ref="profiler"> <aop:pointcut id="serviceMethodWithReturnValue" expression="execution(!void x.y..*Service.*(..))"/> <aop:around method="profile" pointcut-ref="serviceMethodWithReturnValue"/> </aop:aspect> </aop:config> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/> <property name="url" value="jdbc:oracle:thin:@rj-t42:1521:elvis"/> <property name="username" value="scott"/> <property name="password" value="tiger"/> </bean> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> </beans>
上面配置的結(jié)果將獲得到一個擁有剖析和事務(wù)方面的 按那樣的順序 應(yīng)用于它上面的 'fooService'
bean。
許多附加的方面的配置將一起達(dá)到這樣的效果。
最后,下面的一些示例演示了使用純XML聲明的方法來達(dá)到上面一樣的設(shè)置效果。
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd"> <bean id="fooService" class="x.y.service.DefaultFooService"/> <!-- the profiling advice --> <bean id="profiler" class="x.y.SimpleProfiler"> <!-- execute before the transactional advice (hence the lower order number) --> <property name="order" value="1"/> </bean> <aop:config> <aop:pointcut id="entryPointMethod" expression="execution(* x.y..*Service.*(..))"/> <!-- will execute after the profiling advice (c.f. the order attribute) --> <aop:advisor advice-ref="txAdvice" pointcut-ref="entryPointMethod" order="2"/> <!-- order value is higher than the profiling aspect --> <aop:aspect id="profilingAspect" ref="profiler"> <aop:pointcut id="serviceMethodWithReturnValue" expression="execution(!void x.y..*Service.*(..))"/> <aop:around method="profile" pointcut-ref="serviceMethodWithReturnValue"/> </aop:aspect> </aop:config> <tx:advice id="txAdvice" transaction-manager="txManager"> <tx:attributes> <tx:method name="get*" read-only="true"/> <tx:method name="*"/> </tx:attributes> </tx:advice> <!-- other <bean/> definitions such as aDataSource
and aPlatformTransactionManager
here --> </beans>
上面配置的結(jié)果是創(chuàng)建了一個 'fooService'
bean,剖析方面和事務(wù)方面被 依照順序 施加其上。如果我們希望剖析通知在目標(biāo)方法執(zhí)行之前 后于 事務(wù)通知執(zhí)行,而且在目標(biāo)方法執(zhí)行之后 先于 事務(wù)通知,我們可以簡單地交換兩個通知bean的order值。
如果配置中包含更多的方面,它們將以同樣的方式受到影響。
通過AspectJ切面,你也可以在Spring容器之外使用Spring框架的 @Transactional
功能。要使用這項功能你必須先給相應(yīng)的類和方法加上 @Transactional
注解,然后把 spring-aspects.jar
文件中定義的 org.springframework.transaction.aspectj.AnnotationTransactionAspect
切面連接進(jìn)(織入)你的應(yīng)用。同樣,該切面必須配置一個事務(wù)管理器。你當(dāng)然可以通過Spring框架容器來處理注入,但因?yàn)槲覀冞@里關(guān)注于在Spring容器之外運(yùn)行應(yīng)用,我們將向你展示如何通過手動書寫代碼來完成。
在我們繼續(xù)之前,你可能需要好好讀一下前面的第?9.5.6?節(jié) “使用 @Transactional
” 和 第?6?章 使用Spring進(jìn)行面向切面編程(AOP)
兩章。
// construct an appropriate transaction manager
DataSourceTransactionManager txManager = new DataSourceTransactionManager(getDataSource());
// configure the AnnotationTransactionAspect
to use it; this must be done before executing any transactional methods
AnnotationTransactionAspect.aspectOf().setTransactionManager(txManager);
使用此切面(aspect),你必須在 實(shí)現(xiàn) 類(和/或類里的方法)、而 不是 類的任何所實(shí)現(xiàn)的接口上面進(jìn)行注解。AspectJ遵循Java的接口上的注解 不被繼承 的規(guī)則。
定義在類上的 @Transactional
注解指定了類中所有方法執(zhí)行時的默認(rèn)事務(wù)語義。
定義在類的方法上的 @Transactional
注解將覆蓋掉類上注解的所指定的默認(rèn)事務(wù)語義(如過存在的話)。
所有的方法都可以注解,不管它的可見度是什么樣的。
要把 AnnotationTransactionAspect
織入你的應(yīng)用,你或者基于AspectJ構(gòu)建你的應(yīng)用(參考 AspectJ Development Guide),或者采取“載入時織入”(load-time weaving),參考 第?6.8.4?節(jié) “在Spring應(yīng)用中使用AspectJ加載時織入(LTW)” 獲得關(guān)于使用AspectJ進(jìn)行“載入時織入”的討論。