Spring聲明式事務管理中的事務回滾

一:使用

本文在spring + spring mvc + mybatis中使用

第一步配置xml:注意xml最前面tx名稱空間一定要配置

<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:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/tx
       http://www.springframework.org/schema/tx/spring-tx.xsd"> 
 <!-- 配置SqlSessionFactory對象 -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">

        <!-- 注入數據庫連接池-->
        <property name="dataSource" ref="localdataSource"/>
        <!-- 設置這個以後再Mapper配置文件中在parameterType的值就不用寫成全路徑名了 -->
        <property name="typeAliasesPackage" value="com.qiqi.juint.model"/>
        <!-- 掃描sql配置文件:mapper需要的xml文件-->
        <property name="mapperLocations" value="classpath:mapper/*.xml"/>

    </bean>  
<!-- 配置事務管理器 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!-- 注入數據庫連接池-->
        <property name="dataSource" ref="localdataSource"/>
    </bean>

    <!-- 配置基於註解的聲明式事務 -->
    <tx:annotation-driven transaction-manager = "transactionManager" />

在這裏,由於org.mybatis.spring.SqlSessionFactoryBean引用的數據源與DataSourceTransactionManager引用的數據源一致,所以MyBatis自動參與到spring事務管理中,無需額外配置。

第二步:

在接口、接口方法、類以及類方法( public方法)上直接添加@Transactional即可。

注意:不要導錯包,使用org.springframework.transaction.annotation.Transactional包,而不是javax.transaction.Transactional

當作用於類上時,該類的所有 public 方法將都具有該類型的事務屬性。在方法上使用該註解會覆蓋類上的定義。

注意:雖然 @Transactional 註解可以作用於接口、接口方法、類以及類方法上,但是 Spring 建議不要在接口或者接口方法上使用該註解,因爲這只有在使用基於接口的代理時它纔會生效。另外, @Transactional 註解應該只被應用到 public 方法上,這是由 Spring AOP 的本質決定的。如果你在 protected、private 或者默認可見性的方法上使用 @Transactional 註解,這將被忽略,也不會拋出任何異常


二:Spring事務回滾原則

Spring事務管理器會捕捉任何未處理的異常,然後依據規則決定是否回滾拋出異常的事務。

默認配置下,Spring只有在拋出的異常爲運行時unchecked異常時纔回滾該事務,也就是拋出的異常爲RuntimeException及其子類以及Errors,拋出checked異常則不會導致事務回滾。

可以明確的配置在拋出哪些異常時回滾事務,包括checked異常。例如:使用@Transactional(rollbackFor=IOException.class)

則拋出IOException異常時也可以回滾事務。也可以明確定義哪些異常拋出時不回滾事務,例如@Transactional(noRollbackFor = IOException.class)

還可以編程性的通過TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()方法來手動地指示一個事務必須回滾。

三:實例分析

Controller層:

@Controller
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserService userService;

    @RequestMapping(value = "jsp1",method = RequestMethod.GET)
    public String getJsp( ){
        return "test";
    }

    @RequestMapping(value = "insertUser", method = RequestMethod.POST)
    public void insertUser(@RequestBody User user)throws Exception {
        userService.insertUser(user);

    }
}

Service層:

public interface UserService {
    void insertUser(User user)throws Exception;
}

@Transactional  
@Service
public class UserServiceImpl implements UserService {

    private static final Logger logger = Logger.getLogger(UserServiceImpl.class);
    @Autowired
    private UserServiceTestTransactionImpl userServiceTestTransaction;
    @Autowired
    private UserMapper userMapper;

    public void insertUser(User user) throws Exception{
//        try {
            userMapper.inserUser(new User(11,"格格A"));
            userServiceTestTransaction.insertUser(user);
//        } catch (Exception e) {
//            e.printStackTrace();
//        }
    }
}


@Transactional(RollbackFor = IOException.class)
@Service
public class UserServiceTestTransactionImpl {

    @Autowired
    private UserMapper userMapper;

    public void insertUser(User user) throws Exception {
        userMapper.inserUser(user);
        throw new IOException();
        //throw new RuntimeException();
        //throw new Error();
    }
}

dao層不再展示。

注意:@Transactional註解默認的事務傳播行爲是REQUIRED。可以通過propagation 屬性指定具體的傳播行爲。例如:@Transactional(rollbackFor = IOException.class ,propagation = Propagation.SUPPORTS)

這裏簡單介紹一下:PROPAGATION_REQUIRED。解釋:在ServiceA.methodA中調用了ServiceB.methodB時,若ServiceB.methodB的事務級別定義爲PROPAGATION_REQUIRED。如果ServiceA.methodA已經起了事務(有@Transactional註解),這時ServiceB.methodB就加入ServiceA.methodA的事務中,就不再起新的事務。而如果ServiceA.methodA運行的時候發現自己沒有在事務中,ServiceA.methodA會爲自己創建一個事務。這樣,在ServiceA.methodA或者在ServiceB.methodB內的任何地方出現異常,事務都會被回滾。即使ServiceB.methodB的事務已經被提交,但是ServiceA.methodA在接下來fail要回滾時,ServiceB.methodB也要回滾。

本文驗證了一下:UserServiceImpl(A)作爲ServiceA,UserServiceTestTransactionImpl(B)作爲ServiceB。

驗證結果如下:

UserServiceTestTransactionImpl(B)中的public方法拋出RuntimeException異常時。

若在UserServiceImpl(A)的方法中Try catch住異常

  • AB都有@Transtractional,B拋出RunTimeexception異常,AB都會回滾
  • A沒有B有@Transtractional,B拋出RunTimeexception異常, A不會回滾 B回滾
  • A有B沒有@Transtractional,B拋出RunTimeexception異常, AB都不會回滾(REQUIRED,B加入A的事務,但是被A捕獲)

若沒有在UserServiceImpl(A)的方法中Try catch住異常

  • A B都有@Transtractional, B拋出RunTimeexception異常,AB都會回滾 
  • A沒有B有@Transtractional, B拋出RunTimeexception異常 ,A不會回滾 B回滾
  • A有B沒有@Transtractional ,B拋出RunTimeexception異常, AB都會回滾(REQUIRED,B加入A的事務,沒有被A捕獲)

 

 

總結:

可以發現,只要我們在Service層每一個類上都加上@Transtractional註解,即:每一個類均開啓事務,不論是否捕獲B拋出的異常,事務都會回滾。

當B由於自己沒有事務而加入A的事務時(此時AB都在A的事務中),若A中沒有捕獲B拋出的異常,則AB都會回滾(這裏證明了事務的傳播規則REQUIRED的特性);此時若A中捕獲了B拋出的異常,則事務失效,AB都不會回滾。

當A沒有事務,而B存在事務時,由於A無法爲自己開啓事務,所以A會因爲沒有事務而不會回滾。B只存在自己的事務中,無論在A中是否被捕獲異常,都會回滾,因爲B在自己的事務中並沒有捕獲異常,而是拋出異常。

爲什麼捕獲了異常,事務就不會回滾呢?

因爲,Spring的聲明式事務管理是基於AOP實現的。

具體原理如下:在應用系統調用聲明@Transactional 的目標方法時,Spring Framework 默認使用 AOP 代理,在代碼運行時生成一個代理對象,根據@Transactional 的屬性配置信息,這個代理對象決定該聲明@Transactional 的目標方法是否由攔截器 TransactionInterceptor 來使用攔截,在 TransactionInterceptor 攔截時,會在在目標方法開始執行之前創建並加入事務,並執行目標方法的邏輯, 最後根據執行情況是否出現異常,利用抽象事務管理器AbstractPlatformTransactionManager 操作數據源 DataSource 提交或回滾事務。Spring AOP 代理有 CglibAopProxy 和 JdkDynamicAopProxy 兩種。

而Spring aop 異常捕獲原理是被攔截的方法需顯式拋出異常並不能經任何處理,這樣aop代理才能捕獲到方法的異常,才能進行回滾。捕獲異常後,aop就發現不了異常,進而不能回滾。

怎麼避免:

方案一:保證所有涉及到的service類均加上事務

方案二:保證A有事務,在A中不要捕獲異常,異常繼續向上拋出

方案三:保證A有事務,並在其捕獲異常後的cathch語句後面加上throw new RuntimeException()語句

方案四:保證A有事務,並在其捕獲異常後的cathch語句後面加上TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()語句

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章