<轉載>
前段時間對Spring的事務配置做了比較深入的研究,在此之間對Spring的事務配置雖說也配置過,但是一直沒有一個清楚的認識。通過這次的學習發覺Spring的事務配置只要把思路理清,還是比較好掌握的。
總結如下:
Spring配置文件中關於事務配置總是由三個組成部分,分別是DataSource、TransactionManager和代理機制這三部分,無論哪種配置方式,一般變化的只是代理機制這部分。
DataSource、TransactionManager這兩部分只是會根據數據訪問方式有所變化,比如使用Hibernate進行數據訪問時,DataSource實際爲SessionFactory,TransactionManager的實現爲HibernateTransactionManager。
具體如下圖:
根據代理機制的不同,總結了五種Spring事務的配置方式,配置文件如下:
第一種方式:每個Bean都有一個代理
[html] view plaincopy?
<?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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="configLocation" value="classpath:hibernate.cfg.xml" />
<property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
</bean>
<!-- 定義事務管理器(聲明式的事務) -->
<bean id="transactionManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<!-- 配置DAO -->
<bean id="userDaoTarget" class="com.bluesky.spring.dao.UserDaoImpl">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<bean id="userDao"
class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<!-- 配置事務管理器 -->
<property name="transactionManager" ref="transactionManager" />
<property name="target" ref="userDaoTarget" />
<property name="proxyInterfaces" value="com.bluesky.spring.dao.GeneratorDao" />
<!-- 配置事務屬性 -->
<property name="transactionAttributes">
<props>
<prop key="*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
</beans>
第二種方式:所有Bean共享一個代理基類
[html] view plaincopy?
<?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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="configLocation" value="classpath:hibernate.cfg.xml" />
<property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
</bean>
<!-- 定義事務管理器(聲明式的事務) -->
<bean id="transactionManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<bean id="transactionBase"
class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"
lazy-init="true" abstract="true">
<!-- 配置事務管理器 -->
<property name="transactionManager" ref="transactionManager" />
<!-- 配置事務屬性 -->
<property name="transactionAttributes">
<props>
<prop key="*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
<!-- 配置DAO -->
<bean id="userDaoTarget" class="com.bluesky.spring.dao.UserDaoImpl">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<bean id="userDao" parent="transactionBase" >
<property name="target" ref="userDaoTarget" />
</bean>
</beans>
第三種方式:使用攔截器
[html] view plaincopy?
<?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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="configLocation" value="classpath:hibernate.cfg.xml" />
<property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
</bean>
<!-- 定義事務管理器(聲明式的事務) -->
<bean id="transactionManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<bean id="transactionInterceptor"
class="org.springframework.transaction.interceptor.TransactionInterceptor">
<property name="transactionManager" ref="transactionManager" />
<!-- 配置事務屬性 -->
<property name="transactionAttributes">
<props>
<prop key="*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="beanNames">
<list>
<value>*Dao</value>
</list>
</property>
<property name="interceptorNames">
<list>
<value>transactionInterceptor</value>
</list>
</property>
</bean>
<!-- 配置DAO -->
<bean id="userDao" class="com.bluesky.spring.dao.UserDaoImpl">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
</beans>
第四種方式:使用tx標籤配置的攔截器
[html] view plaincopy?
<?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:context="http://www.springframework.org/schema/context"
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/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
<context:annotation-config />
<context:component-scan base-package="com.bluesky" />
<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="configLocation" value="classpath:hibernate.cfg.xml" />
<property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
</bean>
<!-- 定義事務管理器(聲明式的事務) -->
<bean id="transactionManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*" propagation="REQUIRED" />
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="interceptorPointCuts"
expression="execution(* com.bluesky.spring.dao.*.*(..))" />
<aop:advisor advice-ref="txAdvice"
pointcut-ref="interceptorPointCuts" />
</aop:config>
</beans>
第五種方式:全註解
[html] view plaincopy?
<?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:context="http://www.springframework.org/schema/context"
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/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
<context:annotation-config />
<context:component-scan base-package="com.bluesky" />
<tx:annotation-driven transaction-manager="transactionManager"/>
<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="configLocation" value="classpath:hibernate.cfg.xml" />
<property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
</bean>
<!-- 定義事務管理器(聲明式的事務) -->
<bean id="transactionManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
</beans>
此時在DAO上需加上@Transactional註解,如下:
[java] view plaincopy?
package com.bluesky.spring.dao;
import java.util.List;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
import org.springframework.stereotype.Component;
import com.bluesky.spring.domain.User;
@Transactional
@Component("userDao")
public class UserDaoImpl extends HibernateDaoSupport implements UserDao {
public List<User> listUsers() {
return this.getSession().createQuery("from User").list();
}
}
Spring聲明式事務讓我們從複雜的事務處理中得到解脫。使得我們再也無需要去處理獲得連接、關閉連接、事務提交和回滾等這些操作。再也無需要我們在與事務相關的方法中處理大量的try…catch…finally代碼。
我們在使用Spring聲明式事務時,有一個非常重要的概念就是事務屬性。事務屬性通常由事務的傳播行爲,事務的隔離級別,事務的超時值和事務只讀標誌組成。我們在進行事務劃分時,需要進行事務定義,也就是配置事務的屬性。
Spring在TransactionDefinition接口中定義這些屬性,以供PlatfromTransactionManager使用, PlatfromTransactionManager是spring事務管理的核心接口。
[java] view plaincopy?
1. TransactionDefinition
2. public interface TransactionDefinition {
3. int getPropagationBehavior();
4. int getIsolationLevel();
5. int getTimeout();
6. boolean isReadOnly();
7. }
getTimeout()方法,它返回事務必須在多少秒內完成。
isReadOnly(),事務是否只讀,事務管理器能夠根據這個返回值進行優化,確保事務是隻讀的。
getIsolationLevel()方法返回事務的隔離級別,事務管理器根據它來控制另外一個事務可以看到本事務內的哪些數據。
在TransactionDefinition接口中定義了五個不同的事務隔離級別
ISOLATION_DEFAULT 這是一個PlatfromTransactionManager默認的隔離級別,使用數據庫默認的事務隔離級別.另外四個與JDBC的隔離級別相對應
ISOLATION_READ_UNCOMMITTED 這是事務最低的隔離級別,它充許別外一個事務可以看到這個事務未提交的數據。這種隔離級別會產生髒讀,不可重複讀和幻像讀。
例如:
Mary的原工資爲1000,財務人員將Mary的工資改爲了8000,但未提交事務
Java代碼
1. Connection con1 = getConnection();
2. con.setAutoCommit(false);
3. update employee set salary = 8000 where empId ="Mary";
與此同時,Mary正在讀取自己的工資
Java代碼
1. Connection con2 = getConnection();
2. select salary from employee where empId ="Mary";
3. con2.commit();
Mary發現自己的工資變爲了8000,歡天喜地!
而財務發現操作有誤,而回滾了事務,Mary的工資又變爲了1000
Java代碼
1. //con1
2. con1.rollback();
像這樣,Mary記取的工資數8000是一個髒數據。
ISOLATION_READ_COMMITTED 保證一個事務修改的數據提交後才能被另外一個事務讀取。另外一個事務不能讀取該事務未提交的數據。這種事務隔離級別可以避免髒讀出現,但是可能會出現不可重複讀和幻像讀。
ISOLATION_REPEATABLE_READ 這種事務隔離級別可以防止髒讀,不可重複讀。但是可能出現幻像讀。它除了保證一個事務不能讀取另一個事務未提交的數據外,還保證了避免下面的情況產生(不可重複讀)。
在事務1中,Mary 讀取了自己的工資爲1000,操作並沒有完成
Java代碼
1. con1 = getConnection();
2. select salary from employee empId ="Mary";
在事務2中,這時財務人員修改了Mary的工資爲2000,並提交了事務.
Java代碼
1. con2 = getConnection();
2. update employee set salary = 2000;
3. con2.commit();
在事務1中,Mary 再次讀取自己的工資時,工資變爲了2000
Java代碼
1. //con1
2. select salary from employee empId ="Mary";
在一個事務中前後兩次讀取的結果並不致,導致了不可重複讀。
使用ISOLATION_REPEATABLE_READ可以避免這種情況發生。
ISOLATION_SERIALIZABLE 這是花費最高代價但是最可靠的事務隔離級別。事務被處理爲順序執行。除了防止髒讀,不可重複讀外,還避免了幻像讀。
目前工資爲1000的員工有10人。
事務1,讀取所有工資爲1000的員工。
Java代碼
1. con1 = getConnection();
2. Select * from employee where salary =1000;
共讀取10條記錄
這時另一個事務向employee表插入了一條員工記錄,工資也爲1000
Java代碼
1. con2 = getConnection();
2. Insert into employee(empId,salary) values("Lili",1000);
3. con2.commit();
事務1再次讀取所有工資爲1000的員工
Java代碼
1. //con1
2. select * from employee where salary =1000;
共讀取到了11條記錄,這就產生了幻像讀。
ISOLATION_SERIALIZABLE能避免這樣的情況發生。但是這樣也耗費了最大的資源。
getPropagationBehavior()返回事務的傳播行爲,由是否有一個活動的事務來決定一個事務調用。
在TransactionDefinition接口中定義了七個事務傳播行爲。
PROPAGATION_REQUIRED 如果存在一個事務,則支持當前事務。如果沒有事務則開啓一個新的事務。
Java代碼
1. //事務屬性 PROPAGATION_REQUIRED
2. methodA{
3. ……
4. methodB();
5. ……
6. }
7.
8. //事務屬性 PROPAGATION_REQUIRED
9. methodB{
10. ……
11.}
使用spring聲明式事務,spring使用AOP來支持聲明式事務,會根據事務屬性,自動在方法調用之前決定是否開啓一個事務,並在方法執行之後決定事務提交或回滾事務。
單獨調用methodB方法
Java代碼
1. main{
2. metodB();
3. }
相當於
Java代碼
1. Main{
2. Connection con=null;
3.
4. rry{
5. con = getConnection();
6. con.setAutoCommit(false);
7. //方法調用
8. methodB();
9. //提交事務
10.con.commit();
11.}
12.Catch(RuntimeException ex){
13. //回滾事務
14. con.rollback();
15.}
16.finally{
17. //釋放資源
18. closeCon();
19.}
20.}
Spring保證在methodB方法中所有的調用都獲得到一個相同的連接。在調用methodB時,沒有一個存在的事務,所以獲得一個新的連接,開啓了一個新的事務。
單獨調用MethodA時,在MethodA內又會調用MethodB.
執行效果相當於
Java代碼
1. main{
2. Connection con = null;
3. try{
4. con = getConnection();
5. methodA();
6. con.commit();
7. }
8. cathc(RuntimeException ex){
9. con.rollback();
10.}
11.finally{
12. closeCon();
13.}
14.}
調用MethodA時,環境中沒有事務,所以開啓一個新的事務.
當在MethodA中調用MethodB時,環境中已經有了一個事務,所以methodB就加入當前事務。
PROPAGATION_SUPPORTS 如果存在一個事務,支持當前事務。如果沒有事務,則非事務的執行。但是對於事務同步的事務管理器,PROPAGATION_SUPPORTS與不使用事務有少許不同。
Java代碼
1. //事務屬性 PROPAGATION_REQUIRED
2. methodA(){
3. methodB();
4. }
5.
6. //事務屬性 PROPAGATION_SUPPORTS
7. methodB(){
8. ……
9. }
單純的調用methodB時,methodB方法是非事務的執行的。
當調用methdA時,methodB則加入了methodA的事務中,事務地執行。
PROPAGATION_MANDATORY 如果已經存在一個事務,支持當前事務。如果沒有一個活動的事務,則拋出異常。
Java代碼
1. //事務屬性 PROPAGATION_REQUIRED
2. methodA(){
3. methodB();
4. }
5.
6. //事務屬性 PROPAGATION_MANDATORY
7. methodB(){
8. ……
9. }
當單獨調用methodB時,因爲當前沒有一個活動的事務,則會拋出異常
throw new IllegalTransactionStateException("Transactionpropagation 'mandatory' but no existing transaction found");
當調用methodA時,methodB則加入到methodA的事務中,事務地執行。
PROPAGATION_REQUIRES_NEW 總是開啓一個新的事務。如果一個事務已經存在,則將這個存在的事務掛起。
Java代碼
1. //事務屬性 PROPAGATION_REQUIRED
2. methodA(){
3. doSomeThingA();
4. methodB();
5. doSomeThingB();
6. }
7.
8. //事務屬性 PROPAGATION_REQUIRES_NEW
9. methodB(){
10. ……
11.}
當單獨調用methodB時,相當於把methodb聲明爲REQUIRED。開啓一個新的事務,事務地執行。
當調用methodA時
Java代碼
1. main(){
2. methodA();
3. }
情況有些大不一樣.相當於下面的效果。
Java代碼
1. main(){
2. TransactionManager tm = null;
3. try{
4. //獲得一個JTA事務管理器
5. tm = getTransactionManager();
6. tm.begin();//開啓一個新的事務
7. Transaction ts1 = tm.getTransaction();
8. doSomeThing();
9. tm.suspend();//掛起當前事務
10. try{
11. tm.begin();//重新開啓第二個事務
12. Transaction ts2 = tm.getTransaction();
13. methodB();
14. ts2.commit();//提交第二個事務
15.
16. }
17. Catch(RunTimeException ex){
18. ts2.rollback();//回滾第二個事務
19. }
20. finally{
21. //釋放資源
22. }
23. //methodB執行完後,復恢第一個事務
24. tm.resume(ts1);
25.doSomeThingB();
26. ts1.commit();//提交第一個事務
27.}
28.catch(RunTimeException ex){
29. ts1.rollback();//回滾第一個事務
30.}
31.finally{
32. //釋放資源
33.}
34.}
在這裏,我把ts1稱爲外層事務,ts2稱爲內層事務。從上面的代碼可以看出,ts2與ts1是兩個獨立的事務,互不相干。Ts2是否成功並不依賴於
ts1。如果methodA方法在調用methodB方法後的doSomeThingB方法失敗了,而methodB方法所做的結果依然被提交。而除了
methodB之外的其它代碼導致的結果卻被回滾了。
使用PROPAGATION_REQUIRES_NEW,需要使用JtaTransactionManager作爲事務管理器。
PROPAGATION_NOT_SUPPORTED 總是非事務地執行,並掛起任何存在的事務。
Java代碼
1. //事務屬性 PROPAGATION_REQUIRED
2. methodA(){
3. doSomeThingA();
4. methodB();
5. doSomeThingB();
6. }
7.
8. //事務屬性 PROPAGATION_NOT_SUPPORTED
9. methodB(){
10. ……
11.}
當單獨調用methodB時,不啓用任何事務機制,非事務地執行。
當調用methodA時,相當於下面的效果
Java代碼
1. main(){
2. TransactionManager tm = null;
3. try{
4. //獲得一個JTA事務管理器
5. tm = getTransactionManager();
6. tm.begin();//開啓一個新的事務
7. Transaction ts1 = tm.getTransaction();
8. doSomeThing();
9. tm.suspend();//掛起當前事務
10. methodB();
11. //methodB執行完後,復恢第一個事務
12. tm.resume(ts1);
13.doSomeThingB();
14. ts1.commit();//提交第一個事務
15.}
16.catch(RunTimeException ex){
17. ts1.rollback();//回滾第一個事務
18.}
19.finally{
20. //釋放資源
21.}
22.}
使用PROPAGATION_NOT_SUPPORTED,也需要使用JtaTransactionManager作爲事務管理器。
PROPAGATION_NEVER 總是非事務地執行,如果存在一個活動事務,則拋出異常
Java代碼
1. //事務屬性 PROPAGATION_REQUIRED
2. methodA(){
3. doSomeThingA();
4. methodB();
5. doSomeThingB();
6. }
7.
8. //事務屬性 PROPAGATION_NEVER
9. methodB(){
10. ……
11.}
單獨調用methodB,則非事務的執行。
調用methodA則會拋出異常
throw new IllegalTransactionStateException(
"Transaction propagation 'never' butexisting transaction found");
PROPAGATION_NESTED如果一個活動的事務存在,則運行在一個嵌套的事務中. 如果沒有活動事務, 則按TransactionDefinition.PROPAGATION_REQUIRED 屬性執行
這是一個嵌套事務,使用JDBC 3.0驅動時,僅僅支持DataSourceTransactionManager作爲事務管理器。需要JDBC 驅動的java.sql.Savepoint類。有一些JTA的事務管理器實現可能也提供了同樣的功能。
使用PROPAGATION_NESTED,還需要把PlatformTransactionManager的nestedTransactionAllowed屬性設爲true;
而nestedTransactionAllowed屬性值默認爲false;
Java代碼
1. //事務屬性 PROPAGATION_REQUIRED
2. methodA(){
3. doSomeThingA();
4. methodB();
5. doSomeThingB();
6. }
7.
8. //事務屬性 PROPAGATION_NESTED
9. methodB(){
10. ……
11.}
如果單獨調用methodB方法,則按REQUIRED屬性執行。
如果調用methodA方法,相當於下面的效果
Java代碼
1. main(){
2. Connection con = null;
3. Savepoint savepoint = null;
4. try{
5. con = getConnection();
6. con.setAutoCommit(false);
7. doSomeThingA();
8. savepoint = con2.setSavepoint();
9. try
10. methodB();
11. }catch(RuntimeException ex){
12. con.rollback(savepoint);
13. }
14. finally{
15. //釋放資源
16. }
17.
18. doSomeThingB();
19. con.commit();
20.}
21.catch(RuntimeException ex){
22. con.rollback();
23.}
24.finally{
25. //釋放資源
26.}
27.}
當methodB方法調用之前,調用setSavepoint方法,保存當前的狀態到savepoint。如果methodB方法調用失敗,則恢復
到之前保存的狀態。但是需要注意的是,這時的事務並沒有進行提交,如果後續的代碼(doSomeThingB()方法)調用失敗,則回滾包括
methodB方法的所有操作。
嵌套事務一個非常重要的概念就是內層事務依賴於外層事務。外層事務失敗時,會回滾內層事務所做的動作。而內層事務操作失敗並不會引起外層事務的回滾。
PROPAGATION_NESTED與PROPAGATION_REQUIRES_NEW的區別:它們非常類似,都
像一個嵌套事務,如果不存在一個活動的事務,都會開啓一個新的事務。使用PROPAGATION_REQUIRES_NEW時,內層事務與外層事務就像兩
個獨立的事務一樣,一旦內層事務進行了提交後,外層事務不能對其進行回滾。兩個事務互不影響。兩個事務不是一個真正的嵌套事務。同時它需要JTA事務管理
器的支持。
使用PROPAGATION_NESTED時,外層事務的回滾可以引起內層事務的回滾。而內層事務的異常並不會導致外層事務的回滾,它是一個真正的嵌套事
務。DataSourceTransactionManager使用savepoint支持PROPAGATION_NESTED時,需要JDBC
3.0以上驅動及1.4以上的JDK版本支持。其它的JTA TrasactionManager實現可能有不同的支持方式。
PROPAGATION_REQUIRED應該是我們首先的事務傳播行爲。它能夠滿足我們大多數的事務需求。
[java] view plaincopy?
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = { AppBizExeA.class } , noRollbackFor = { AppBizExeB.class })
public void method1() throws Exception {
System.out.println("method1 start");
TPerson per = new TPerson();
per.setAge("24");
per.setId(123);
per.setName("xj");
personDao.add(per);
throw new NullPointerException();
}
@Transactional(propagation = Propagation.NESTED, rollbackFor = { AppBizExeA.class })
public void method2() throws Exception {
System.out.println("method2 start");
TPerson per = new TPerson();
per.setAge("24");
per.setId(1234);
per.setName("xj");
personDao.add(per);
System.out.println("method2 end");
}
Java代碼
1. public static void main(String[] args) throws Exception {
2.
3. ApplicationContext context = new ClassPathXmlApplicationContext(new String[] { "classpath:com/benx/benx.xml" });
4. PersonService service = (PersonService) context.getBean("personService");
5. service.method1();
6.
7. }
今天只談傳播行爲爲REQUIRED的,其他先不談,因爲使用比較少,而且支持也不夠好
1、執行service.method1(),不管method裏面是否嵌套了method2或其他,事務都是以 method1開始和結尾,且事務配置同樣以method1爲準,忽略其他REQUIRED行爲的配置,比如異常機制,以method1爲準,忽略 method2的異常配置
2、Transactional的異常控制,默認是Check Exception 不回滾,unCheck Exception回滾
3、如果配置了rollbackFor 和 noRollbackFor 且兩個都是用同樣的異常,那麼遇到該異常,還是回滾
4、rollbackFor 和noRollbackFor 配置也許不會含蓋所有異常,對於遺漏的按照Check Exception 不回滾,unCheck Exception回滾
@Transactional只能被應用到public方法上,對於其它非public的方法,如果標記了@Transactional也不會報錯,但方法沒有事務功能.
Spring使用聲明式事務處理,默認情況下,如果被註解的數據庫操作方法中發生了unchecked異常,所有的數據庫操作將rollback;如果發生的異常是checked異常,默認情況下數據庫操作還是會提交的。
這種默認的行爲是可以改變的。
使用@Transactional註解的noRollbackFor和rollbackFor屬性
如:@Transactional(rollbackFor=Exception.class)可以使checked異常發生時,數據庫操作也rollback、@Transactional(noRollbackFor=RuntimeException.class)可以使unchecked異常發生時也提交數據庫操作。
也可以使用noRollbackForClassName、rollbackForClassName屬性來指定一個異常類名的String數組來改變默認的行爲。
讀取數據庫中的數據時是不需要事務管理的,這種情況下可以使用事務的傳播行爲來告訴Spring不需要開啓事務,
如:@Transactional(propagation =Propagation.NOT_SUPPORTED)。
事務的傳播行爲有:
1. REQUIRED:表示業務方法需要在一個事務中處理,如果業務方法執行時已經在一個事務中,則加入該事務,否則重新開啓一個事務。這也是默認的事務傳播行爲;
2. NOT_SUPPORTED:聲明業務方法不需要事務,如果業務方法執行時已經在一個事務中,則事務被掛起,等方法執行完畢後,事務恢復進行;
3. REQUIRES_NEW:表明業務方法需要在一個單獨的事務中進行,如果業務方法進行時已經在一個事務中,則這個事務被掛起,並重新開啓一個事務來執行這個業務方法,業務方法執行完畢後,原來的事務恢復進行;
4. MANDATORY:該屬性指定業務方法只能在一個已經存在的事務中進行,業務方法不能發起自己的事務;如果業務方法沒有在一個既有的事務中進行,容器將拋出異常;
5. SUPPORTS:該屬性指定,如果業務方法在一個既有的事務中進行,則加入該事務;否則,業務方法將在一個沒有事務的環境下進行;
6. NEVER:指定業務方法不可以在事務中進行,如果業務方法執行時已經在一個事務中,容器將拋出異常;
7. NESTED:該屬性指定,如果業務方法在一個既有的事務中執行,則該業務方法將在一個嵌套的事務中進行;否則,按照REQUEIRED來對待。它使用一個單獨的事務,這個事務可以有多個rollback點,內部事務的rollback對外部事務沒有影響,但外部事務的rollback會導致內部事務的rollback。這個行爲只對DataSourceTransactionManager有效。
事務的隔離級別
使用@Transactional的Isolation屬性可以指定事務的隔離級別。但事務的隔離級別是由底層的數據庫實現的,並不是由Spring來實現。
1. READ_UNCOMMITTED:會出現髒讀、不可重複讀和幻讀問題;
2. READ_COMMITTED:會出現不可重複讀和幻讀問題;
3. REPEATABLE_READ:會出現幻讀問題;
4. SERIALIZABLE:串行化,不會出現上面的問題。
一般的數據庫默認提供的是READ_COMMITTED隔離級別,如sqlserver2000;Mysql默認提供的是REPEATABLE_READ。
@Transactional 的所有可選屬性如下:
屬性 類型 默認值
說明
propagation Propagation枚舉 REQUIRED 事務傳播屬性
isolation isolation枚舉 DEFAULT 事務隔離級別
readOnly boolean false 是否只讀
timeout int -1 超時(秒)
rollbackFor Class[] {} 需要回滾的異常類
rollbackForClassName String[] {} 需要回滾的異常類名
noRollbackFor Class[] {} 不需要回滾的異常類
noRollbackForClassName String[] {} 不需要回滾的異常類名
//事務傳播屬性
@Transactional(propagation=Propagation.REQUIRED) //如果有事務,那麼加入事務,沒有的話新建一個(不寫的情況下)
@Transactional(propagation=Propagation.NOT_SUPPORTED) //容器不爲這個方法開啓事務
@Transactional(propagation=Propagation.REQUIRES_NEW) //不管是否存在事務,都創建一個新的事務,原來的掛起,新的執行完畢,繼續執行老的事務
@Transactional(propagation=Propagation.MANDATORY) //必須在一個已有的事務中執行,否則拋出異常
@Transactional(propagation=Propagation.NEVER) //必須在一個沒有的事務中執行,否則拋出異常(與Propagation.MANDATORY相反)
@Transactional(propagation=Propagation.SUPPORTS) //如果其他bean調用這個方法,在其他bean中聲明事務,那就用事務.如果其他bean沒有聲明事務,那就不用事務.
@Transactional(propagation=Propagation.NESTED)
@Transactional (propagation =Propagation.REQUIRED,readOnly=true) //readOnly=true只讀,不能更新,刪除
@Transactional (propagation =Propagation.REQUIRED,timeout=30)//設置超時時間
@Transactional (propagation =Propagation.REQUIRED,isolation=Isolation.DEFAULT)//設置數據庫隔離級別
用 spring事務管理器,由spring來負責數據庫的打開,提交,回滾.
默認遇到運行期例外(throw new RuntimeException("註釋");)會回滾,即遇到不受檢查(unchecked)的例外時回滾;
而遇到需要捕獲的例外(throw new Exception("註釋");)不會回滾,即遇到受檢查的例外(就是非運行時拋出的異常,編譯器會檢查到的異常叫受檢查例外或說受檢查異常)時,需我們指定方式來讓事務回滾
,如下:
@Transactional(rollbackFor=Exception.class) //指定回滾,遇到異常Exception時回滾
public void methodName() {
throw new Exception("註釋");
}
@Transactional(noRollbackFor=Exception.class)//指定不回滾,遇到運行期例外(throw
newRuntimeException("註釋");)會回滾
public ItimDaoImpl getItemDaoImpl() {
throw new RuntimeException("註釋");
}