运用@Transactional,自己抛出异常时不会回滚的原因

来源:互联网 发布:淘宝买家信用等级良好 编辑:程序博客网 时间:2024/05/16 22:27

问题

        使用spring的配置事物注解@Transactional,在测试的时候发现不起作用。

环境

       

         配置文件

[html] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. <bean id="studentMGDataSource" class="org.apache.commons.dbcp.BasicDataSource"  
  2.     destroy-method="close">  
  3.     <property name="driverClassName" value="${student_MG_jdbc.driver}" />  
  4.     <property name="url" value="${student_MG_jdbc.url}" />  
  5.     <property name="username" value="${student_MG_jdbc.username}" />  
  6.     <property name="password" value="${student_MG_jdbc.password}" />  
  7.     <property name="initialSize" value="${student_MG_jdbc.initialSize}" />  
  8.     <property name="maxActive" value="${student_MG_jdbc.maxActive}" />  
  9.     <property name="maxIdle" value="${student_MG_jdbc.maxIdle}" />  
  10.     <property name="maxWait" value="${student_MG_jdbc.maxWait}" />  
  11.     <property name="defaultAutoCommit" value="${student_MG_jdbc.defaultAutoCommit}" />  
  12. </bean>  
  13.   
  14. <bean id="studentMGSqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">  
  15.     <property name="configLocation" value="classpath:mybatis/mybatis-studentMG-config.xml" />  
  16.     <property name="dataSource" ref="studentMGDataSource" />  
  17. </bean>  
  18.   
  19. <bean id="studentMGSqlSession" class="org.mybatis.spring.SqlSessionTemplate">  
  20.     <constructor-arg index="0" ref="studentMGSqlSessionFactory" />  
  21. </bean>  
  22.   
  23. <bean id="studentMGTxManager"  
  24.     class="org.springframework.jdbc.datasource.DataSourceTransactionManager">  
  25.     <property name="dataSource" ref="studentMGDataSource" />  
  26. </bean>  
  27.   
  28. <tx:annotation-driven proxy-target-class="true" transaction-manager="studentMGTxManager" />  

        Java代码

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. @Transactional(value="studentMGTxManager",rollbackFor=java.lang.Exception.class)  
  2. public void saveStudentDto(List<StudentDto> dtoList, String classId) {  
  3.       
  4. }  

原因

        数据库使用的存储引擎是MyISam,MyISam不支持事物,应该用InnoDB引擎

TIPS

        @Transactional注解事务不起作用的解决
         可能的原因:
        1.数据库引擎要支持事务
        如果是MySQL,注意表要使用支持事务的引擎,比如innodb,如果是myisam,事务是不起作用的

        2.是否开启了对注解的解析

        配置文件必须加<tx:annotation-driven />,否则不解析@Transactional




假如读者是在整合j2EE的三大框架式遇到这个问题,那应该就是下面这个问题:

   

    我记得当时我遇到这个问题的时候,是因为数据库的表,不支持事务!如果mysql不支持存储引擎,它将以MyISAM表创建表,这是非事务性表。一般修改成InnoDB.

 

    假如有兴趣了解 mysql中 " engine=innodb " 以及 " engine=innodb 和engine=myisam的区别 ",可以读读这篇文章,或许对读者有帮助:http://blog.sina.com.cn/s/blog_6ac4c6cb01018pb1.html

 

    可使用下述语句之一检查表的标类型: 

    SHOW TABLE STATUS LIKE 'tbl_name';
    SHOW CREATE TABLE tbl_name;

  使用下述语句,可检查mysqld服务器支持的存储引擎: 
   

    SHOW ENGINES;
 

    也可以使用下述语句,检查与你感兴趣的存储引擎有关的变量值: 

    SHOW VARIABLES LIKE 'have_%';

  例如,要想确定InnoDB存储引擎是否可用,可检查have_innodb变量的值。

 

 

 二 、假如读者不是上述情况,那请研读下面这段:

--------------------------------------------------------------------------------------------------

 

近日测试用例,发现这样一个现象:


在业务代码中,有如下两种情况,比如:
throw new RuntimeException("xxxxxxxxxxxx"); 事务回滚
throw new Exception("xxxxxxxxxxxx"); 事务没有回滚

 

自以为很了解事务,或许时间久远的缘故,没分析出来何故,遂查阅了下资料,写下了如下的内容,供参考:

 

1).Spring的AOP即声明式事务管理默认是针对unchecked exception回滚。也就是默认对RuntimeException()异常或是其子类进行事务回滚;checked异常,即Exception可try{}捕获的不会回滚,如果使用try-catch捕获抛出的unchecked异常后没有在catch块中采用页面硬编码的方式使用spring api对事务做显式的回滚,则事务不会回滚, “将异常捕获,并且在catch块中不对事务做显式提交=生吞掉异常” ,要想捕获非运行时异常则需要如下配置:

解决办法:
1.在针对事务的类中抛出RuntimeException异常,而不是抛出Exception。
2.在txAdive中增加rollback-for,里面写自己的exception,例如自己写的exception:

<tx:advice id="txAdvice" transaction-manager="transactionManager">
   <tx:attributes>
     <tx:method name="*" rollback-for="com.cn.untils.exception.XyzException"/>
   </tx:attributes>
 </tx:advice>
 
或者
定义不会滚的异常


<tx:advice id="txAdvice">
    <tx:attributes>
       <tx:method name="update*" no-rollback-for="IOException"/>
       <tx:method name="*"/>
    </tx:attributes>
 </tx:advice>

 
2).spring的事务边界是在调用业务方法之前开始的,业务方法执行完毕之后来执行commit or rollback(Spring默认取决于是否抛出runtime异常).
 如果抛出runtime exception 并在你的业务方法中没有catch到的话,事务会回滚。 
 一般不需要在业务方法中catch异常,如果非要catch,在做完你想做的工作后(比如关闭文件等)一定要抛出runtime exception,否则spring会将你的操作commit,这样就会产生脏数据.所以你的catch代码是画蛇添足。
 
如:
try  
    //bisiness logic code  
catch(Exception e)  
    //handle the exception  
 

 由此可以推知,在spring中如果某个业务方法被一个 整个包裹起来,则这个业务方法也就等于脱离了spring事务的管理,因为没有任何异常会从业务方法中抛出!全被捕获并吞掉,导致spring异常抛出触发事务回滚策略失效。
 不过,如果在catch代码块中采用页面硬编码的方式使用spring api对事务做显式的回滚,这样写也未尝不可。
 
 3).基于注解的事务:

 Transactional的异常控制,默认是Check Exception 不回滚,unCheck Exception回滚
 如果配置了rollbackFor 和 noRollbackFor 且两个都是用同样的异常,那么遇到该异常,还是回滚
 rollbackFor 和noRollbackFor 配置也许不会含盖所有异常,对于遗漏的按照Check Exception 不回滚,unCheck Exception回滚






在spring的配置文件中,如果数据源的defaultAutoCommit设置为True了,那么方法中如果自己捕获了异常,事务是不会回滚的,如果没有自己捕获异常则事务会回滚,如下例
比如配置文件里有这么条记录:

[html] view plain copy
 print?
  1. <bean id="dataSource" class="xxx">  
  2.    <property name="xxx" value="xxx"/>  
  3.    <property name="xxx" value="xxx"/>  
  4.                      ....  
  5.      <property name="defaultAutoCommit" value="true" />  
  6. </bean>  

那么现在有两个情况
情况1:如果没有在程序中手动捕获异常

[java] view plain copy
 print?
  1. @Transactional(rollbackFor = { Exception.class })  
  2. public void test() throws Exception {  
  3.      doDbStuff1();  
  4.      doDbStuff2();//假如这个操作数据库的方法会抛出异常,现在方法doDbStuff1()对数据库的操作   会回滚。  
  5. }  

情况2:如果在程序中自己捕获了异常

[java] view plain copy
 print?
  1. @Transactional(rollbackFor = { Exception.class })  
  2. public void test() {  
  3.      try {  
  4.         doDbStuff1();  
  5.         doDbStuff2();//假如这个操作数据库的方法会抛出异常,现在方法doDbStuff1()对数据库的操作  不会回滚。  
  6.      } catch (Exception e) {  
  7.            e.printStackTrace();     
  8.      }  
  9. }  

现在如果我们需要手动捕获异常,并且也希望抛异常的时候能回滚肿么办呢?
下面这样写就好了,手动回滚事务:

[java] view plain copy
 print?
  1. @Transactional(rollbackFor = { Exception.class })  
  2. public void test() {  
  3.      try {  
  4.         doDbStuff1();  
  5.         doDbStuff2();  
  6.      } catch (Exception e) {  
  7.           e.printStackTrace();     
  8.           TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();//就是这一句了,加上之后,如果doDbStuff2()抛了异常,                                                                                       //doDbStuff1()是会回滚的  
  9.      }  
  10. }  



0 0
原创粉丝点击