spring事务与回滚相关问题

内容纲要

1      spring事务处理

spring事务配置有多种方式,这里以全注解方式进行介绍。

1.1     前提

spring项目已正常跑通;maven项目;

1.2     spring配置文件修改

增加事务管理器:

<!--TransactionManager定义 -->

       <bean id="transactionManager"

              class="org.springframework.jdbc.datasource.DataSourceTransactionManager">

              <property name="dataSource" ref="dataSource1" />

       </bean>

 

       <bean id="transactionTemplate"

              class="org.springframework.transaction.support.TransactionTemplate">

              <property name="transactionManager" ref="transactionManager" />

       </bean>

      

       <!-- enables scanning for @Transactionalannotations -->

       <tx:annotation-driven transaction-manager="transactionManager" />

<tx:annotation-driven>一共有四个属性如下,

Ø  mode:指定Spring事务管理框架创建通知bean的方式。可用的值有proxy和aspectj。前者是默认值,表示通知对象是个JDK代理;后者表示Spring AOP会使用AspectJ创建代理

Ø  proxy-target-class:如果为true,Spring将创建子类来代理业务类;如果为false,则使用基于接口的代理。(如果使用子类代理,需要在类路径中添加CGLib.jar类库)

Ø  order:如果业务类除事务切面外,还需要织入其他的切面,通过该属性可以控制事务切面在目标连接点的织入顺序。

Ø  transaction-manager:指定到现有的PlatformTransaction Manager bean的引用,通知会使用该引用

1.3     增加@Transactional注解

在需要的函数上增加:

1.       @Transactional 

2.       public void test() throws Exception {  

3.            doDbStuff1();  

4.            doDbStuff2();//假如这个操作数据库的方法会抛出runtimeexception,现在方法doDbStuff1()对数据库的操作会回滚。  

5.       }  

1.4     注意事项

1.        在需要事务管理的地方加@Transactional注解。@Transactional 注解可以被应用于接口定义和接口方法、类定义和类的 public 方法上。

2.        @Transactional注解只能应用到 public 可见度的方法上。 如果你在 protected、private 或者 package-visible 的方法上使用 @Transactional 注解,它也不会报错, 但是这个被注解的方法将不会展示已配置的事务设置。

3.        注意仅仅 @Transactional 注解的出现不足于开启事务行为,它仅仅是一种元数据。必须在配置文件中使用配置元素,才真正开启了事务行为。

4.        Spring团队建议在具体的类(或类的方法)上使用 @Transactional 注解,而不要使用在类所要实现的任何接口上。在接口上使用 @Transactional 注解,只能当你设置了基于接口的代理时它才生效。因为注解是 不能继承 的,这就意味着如果正在使用基于类的代理时,那么事务的设置将不能被基于类的代理所识别,而且对象也将不会被事务代理所包装。

5.        @Transactional 的事务开启,是基于接口的或者是基于类的代理被创建。所以在同一个类中一个方法调用另一个方法是有事务的方法,事务是不会起作用的

6.        Spring使用声明式事务处理,默认情况下,如果被注解的数据库操作方法中发生了unchecked异常,所有的数据库操作将rollback;如果发生的异常是checked异常,默认情况下数据库操作还是会提交的。

1.5     常用参数说明

 参 数 名 称

功 能 描 述

readOnly

该属性用于设置当前事务是否为只读事务,设置为true表示只读,false则表示可读写,默认值为false。例如:@Transactional(readOnly=true)

rollbackFor

该属性用于设置需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,则进行事务回滚。例如:

指定单一异常类:@Transactional(rollbackFor=RuntimeException.class)

指定多个异常类:@Transactional(rollbackFor={RuntimeException.class, Exception.class})

 

 

参 数 名 称

功 能 描 述

rollbackForClassName

该属性用于设置需要进行回滚的异常类名称数组,当方法中抛出指定异常名称数组中的异常时,则进行事务回滚。例如:

指定单一异常类名称:@Transactional(rollbackForClassName="RuntimeException")

指定多个异常类名称:@Transactional(rollbackForClassName={"RuntimeException","Exception"})

noRollbackFor

该属性用于设置不需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,不进行事务回滚。例如:

指定单一异常类:@Transactional(noRollbackFor=RuntimeException.class)

指定多个异常类:@Transactional(noRollbackFor={RuntimeException.class, Exception.class})

noRollbackForClassName

该属性用于设置不需要进行回滚的异常类名称数组,当方法中抛出指定异常名称数组中的异常时,不进行事务回滚。例如:

指定单一异常类名称:@Transactional(noRollbackForClassName="RuntimeException")

指定多个异常类名称:

@Transactional(noRollbackForClassName={"RuntimeException","Exception"})

propagation

该属性用于设置事务的传播行为,具体取值可参考表6-7。

例如:@Transactional(propagation=Propagation.NOT_SUPPORTED,readOnly=true)

isolation

该属性用于设置底层数据库的事务隔离级别,事务隔离级别用于处理多事务并发的情况,通常使用数据库的默认隔离级别即可,基本不需要进行设置

timeout

该属性用于设置事务的超时秒数,默认值为-1表示永不超时

 

1.6     exception不回滚解决方案

1.6.1  原因

1.        Checked异常必须被显式地捕获或者传递,如Basic try-catch-finally Exception Handling一文中所说。而unchecked异常则可以不必捕获或抛出。

2.        Checked异常继承java.lang.Exception类。Unchecked异常继承自java.lang.RuntimeException类。

3.        Runtime Exception 在定义方法时不需要声明会抛出runtime exception在调用这个方法时不需要捕获这个runtime exception runtime exception是从java.lang.RuntimeExceptionjava.lang.Error类衍生出来的。例如:nullpointexceptionIndexOutOfBoundsException就属于runtime exception 

4.        Exception:定义方法时必须声明所有可能会抛出的exception在调用这个方法时,必须捕获它的checked exception,不然就得把它的exception传递下去;exception是从java.lang.Exception类衍生出来的。例如:IOExceptionSQLException就属于Exception

默认情况下,如果被注解的数据库操作方法中发生了unchecked异常,所有的数据库操作将rollback;如果发生的异常是checked异常,默认情况下数据库操作还是会提交的。而Exception是checked异常,所以不会回滚。

1.6.2  解决方案

参数增加如下,即可:

6.       @Transactional(rollbackFor = { Exception.class })  

7.       public void test() throws Exception {  

8.            doDbStuff1();  

9.            doDbStuff2();//假如这个操作数据库的方法会抛出异常,现在方法doDbStuff1()对数据库的操作会回滚。  

10.    }  

1.7     spring +springmvc 注解事务无效解决方案

1.7.1  原因

SpringMVC启动时的配置文件,包含组件扫描、url映射以及设置freemarker参数,让spring不扫描带有@Service注解的类。

为什么要这样设置?因为servlet-context.xml与service-context.xml不是同时加载,如果不进行这样的设置,那么,spring就会将所有带@Service注解的类都扫描到容器中,等到加载service-context.xml的时候,会因为容器已经存在Service类,使得cglib将不对Service进行代理,直接导致的结果就是在service-context中的事务配置不起作用,发生异常时,无法对数据进行回滚。

1.7.2  解决方案

1.       spring mvc 自动扫描注解的时候,不去扫描@Service

1.       <context:component-scanbase-package= "org.cn.xxx">

2.       <context:exclude-filtertype ="annotation" expression="org.springframework.stereotype.Service" />

3.       </context:component-scan>

2. spring 自动扫描注解的时候,不去扫描@Controller

1.       <context:component-scanbase-package ="org.cn.xxx>

2.       <context:exclude-filtertype ="annotation" expression="org.springframework.stereotype.Controller" />

3.       </context:component-scan>

1.8     try catch后事务不回滚解决方案

在Spring的配置文件中,如果数据源的defaultAutoCommit设置为True了,那么方法中如果自己捕获了异常,事务是不会回滚的,如果没有自己捕获异常则事务会回滚。

情况1:如果没有在程序中手动捕获异常,正常回滚

1.       @Transactional(rollbackFor = { Exception.class })  

2.       public void test() throws Exception {  

3.            doDbStuff1();  

4.            doDbStuff2();//假如这个操作数据库的方法会抛出异常,现在方法doDbStuff1()对数据库的操作   会回滚。  

5.       }  

情况2:如果在程序中自己捕获了异常,不会回滚

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.       }  

1.8.1  原因

springaop异常捕获原理:被拦截的方法需显式抛出异常,并不能经任何处理,这样aop代理才能捕获到方法的异常,才能进行回滚

现在如果我们需要手动捕获异常,并且也希望抛异常的时候能回滚肿么办呢?以下给出3种解决方案,供大家参考,项目中使用解决方案3。

1.8.2  解决方案1-不使用try catch

@Transactional所在函数不进行try catch捕获,而是放到上层函数进行异常捕获。

比如@Transactional放在service层,我们在service层不进行异常处理,只抛出,而在controller层进行异常捕获。

1.8.3  解决方案2-在catch中throw

catch后再throw,显示回滚

1.       @Transactional(rollbackFor = { Exception.class })  

2.       public void test() {  

3.            try {  

4.               doDbStuff1();  

5.               doDbStuff2();//假如这个操作数据库的方法会抛出异常,现在方法doDbStuff1()对数据库的操作  不会回滚。  

6.            } catch (Exception e) {  

7.                  e.printStackTrace();    

8.                   throw new Exception(“error”);

9.            }  

10.    }  

1.8.4  解决方案3-手动回滚

TransactionAspectSupport手动回滚事务:

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.    }  

1.9     参考文献

http://blog.csdn.net/andyxuq/article/details/7982143

 

http://www.360doc.com/content/12/1109/18/6161903_246870991.shtml

 

http://my.oschina.net/guanzhenxing/blog/214228

 

http://my.oschina.net/u/205170/blog/152438

发表回复