Spring作爲低侵入的Java EE框架之一,能夠很好地與其他框架進行整合,其中Spring與Hibernate的整合實現的事務管理是常用的一種功能。 所謂事務,就必須具備ACID特性,即原子性、一致性、隔離性和持久性
除了基於XML文件的聲明式事務配置外,你也可以採用基於註解式的事務配置方法。直接在Java源代碼中聲明事務語義的做法讓事務聲明和將受其影響的代碼距離更近了,而且一般來說不會有不恰當的耦合的風險,因爲,使用事務性的代碼幾乎總是被部署在事務環境中。
下面的例子很好地演示了 @Transactional 註解的易用性,隨後解釋其中的細節。先看看其中的類定義:
<!-- 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);
}
當上述的POJO定義在Spring IoC容器裏時,上述bean實例僅僅通過一 行xml配置就可以使它具有事務性的。如下:
<!-- 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.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.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"/>
<!-- a PlatformTransactionManager 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>
提示
實際上,如果你用 'transactionManager'
來定義
PlatformTransactionManager
bean的名字的話,你就可以忽略 <tx:annotation-driven/>
標籤裏的
'transaction-manager'
屬性。 如果
PlatformTransactionManager
bean你要通過其它名稱來注入的話,你必須用 'transaction-manager'
屬性來指定它,如上所示。
@Transactional
註解可以被應用於接口定義和接口方法、類定義和類的
public 方法上。然而,請注意僅僅 @Transactional
註解的出現不足於開啓事務行爲,它僅僅
是一種元數據,能夠被可以識別
@Transactional
註解和上述的配置適當的具有事務行爲的beans所使用。上面的例子中,其實正是
<tx:annotation-driven/>
元素的出現 開啓 了事務行爲。
Spring團隊的建議是你在具體的類(或類的方法)上使用 @Transactional
註解,而不要使用在類所要實現的任何接口上。你當然可以在接口上使用
@Transactional
註解,但是這將只能當你設置了基於接口的代理時它才生效。因爲註解是
不能繼承 的,這就意味着如果你正在使用基於類的代理時,那麼事務的設置將不能被基於類的代理所識別,而且對象也將不會被事務代理所包裝(將被確認爲嚴重的)。因此,請接受Spring團隊的建議並且在具體的類上使用
@Transactional
註解。
注意
當使用 @Transactional
風格的進行聲明式事務定義時,你可以通過
<tx:annotation-driven/>
元素的 "proxy-target-class
" 屬性值來控制是基於接口的還是基於類的代理被創建。如果 "proxy-target-class
" 屬值被設置爲 "true
",那麼基於類的代理將起作用(這時需要CGLIB庫cglib.jar在CLASSPATH中)。如果 "proxy-target-class
"
屬值被設置爲 "false
" 或者這個屬性被省略,那麼標準的JDK基於接口的代理將起作用。
在多數情形下,方法的事務設置將被優先執行。在下列情況下,例如: DefaultFooService
類被註解爲只讀事務,但是,這個類中的
updateFoo(Foo)
方法的 @Transactional
註解的事務設置將優先於類級別註解的事務設置。
@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
}
}
9.5.6.1. @Transactional
有關的設置
@Transactional
註解是用來指定接口、類或方法必須擁有事務語義的元數據。 如:“當一個方法開始調用時就開啓一個新的只讀事務,並停止掉任何現存的事務”。 默認的
@Transactional
設置如下:
-
事務傳播設置是
PROPAGATION_REQUIRED
-
事務隔離級別是
ISOLATION_DEFAULT
-
事務是 讀/寫
-
事務超時默認是依賴於事務系統的,或者事務超時沒有被支持。
-
任何
RuntimeException
將觸發事務回滾,但是任何 checkedException
將不觸發事務回滾
這些默認的設置當然也是可以被改變的。 @Transactional
註解的各種屬性設置總結如下:
表 9.2. @Transactional
註解的屬性
屬性 | 類型 | 描述 |
---|---|---|
傳播性 |
枚舉型:Propagation |
可選的傳播性設置 |
隔離性 |
枚舉型:Isolation |
可選的隔離性級別(默認值:ISOLATION_DEFAULT ) |
只讀性 |
布爾型 | 讀寫型事務 vs. 只讀型事務 |
超時 |
int型(以秒爲單位) | 事務超時 |
回滾異常類(rollbackFor) |
一組 Class 類的實例,必須是Throwable 的子類 |
一組異常類,遇到時 必須 進行回滾。默認情況下checked exceptions不進行回滾,僅unchecked exceptions(即RuntimeException 的子類)才進行事務回滾。 |
回滾異常類名(rollbackForClassname) |
一組 Class 類的名字,必須是Throwable 的子類 |
一組異常類名,遇到時 必須 進行回滾 |
不回滾異常類(noRollbackFor) |
一組 Class 類的實例,必須是Throwable 的子類 |
一組異常類,遇到時 必須不 回滾。 |
不回滾異常類名(noRollbackForClassname) |
一組 Class 類的名字,必須是Throwable 的子類 |
一組異常類,遇到時 必須不 回滾 |
考慮這樣的情況,你有一個類的實例,而且希望 同時插入事務性通知(advice)和一些簡單的剖析(profiling)通知。那麼,在<tx:annotation-driven/>
環境中該怎麼做?
我們調用 updateFoo(Foo)
方法時希望這樣:
-
配置的剖析切面(profiling aspect)開始啓動,
-
然後進入事務通知(根據配置創建一個新事務或加入一個已經存在的事務),
-
然後執行原始對象的方法,
-
然後事務提交(我們假定這裏一切正常),
-
最後剖析切面報告整個事務方法執行過程花了多少時間。
注意
這章不是專門講述AOP的任何細節(除了應用於事務方面的之外)。請參考 第 6 章 使用Spring進行面向切面編程(AOP) 章以獲得對各種AOP配置及其一般概念的詳細敘述。
這裏有一份簡單的剖析切面(profiling aspect)的代碼。(注意,通知的順序是由
Ordered
接口來控制的。要想了解更多細節,請參考
第 6.2.4.7 節 “通知(Advice)順序” 節。)
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.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.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"/>
<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>
上面配置的結果將獲得到一個擁有剖析和事務方面的 按那樣的順序 應用於它上面的
'fooService'
bean。 許多附加的方面的配置將一起達到這樣的效果。
最後,下面的一些示例演示了使用純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.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.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>
上面配置的結果是創建了一個 'fooService'
bean,剖析方面和事務方面被
依照順序 施加其上。如果我們希望剖析通知在目標方法執行之前 後於 事務通知執行,而且在目標方法執行之後
先於 事務通知,我們可以簡單地交換兩個通知bean的order值。
如果配置中包含更多的方面,它們將以同樣的方式受到影響。
通過AspectJ切面,你也可以在Spring容器之外使用Spring框架的 @Transactional
功能。要使用這項功能你必須先給相應的類和方法加上
@Transactional
註解,然後把 spring-aspects.jar
文件中定義的
org.springframework.transaction.aspectj.AnnotationTransactionAspect
切面連接進(織入)你的應用。同樣,該切面必須配置一個事務管理器。你當然可以通過Spring框架容器來處理注入,但因爲我們這裏關注於在Spring容器之外運行應用,我們將向你展示如何通過手動書寫代碼來完成。
注意
在我們繼續之前,你可能需要好好讀一下前面的第 9.5.6 節 “使用
@Transactional
” 和
第 6 章 使用Spring進行面向切面編程(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),你必須在 實現 類(和/或類裏的方法)、而 不是 類的任何所實現的接口上面進行註解。AspectJ遵循Java的接口上的註解 不被繼承 的規則。
類上的 @Transactional
註解指定了類裏的任何
public 方法執行的默認事務語義。
類裏的方法的 @Transactional
將覆蓋掉類註解的默認事務語義(如何存在的話)。
public
、protected
和默認可見的方法可能都被註解。直接對
protected
和默認可見的方法進行註解,讓這些方法在執行時去獲取所定義的事務劃分是唯一的途徑。
要把 AnnotationTransactionAspect
織入你的應用,你或者基於AspectJ構建你的應用(參考
AspectJ Development Guide),或者採取“載入時織入”(load-time weaving),參考
第 6.8.4 節 “在Spring應用中使用AspectJ Load-time weaving(LTW)” 獲得關於使用AspectJ進行“載入時織入”的討論。