事务(@Transactional注解)的用法和实例

声明式事务@Transactional例子

Posted by Zwx on October 29, 2018

参数

@Transactional可以配制那些参数及以其所代表的意义:

参数 意义
isolation 事务隔离级别
propagation 事务传播机制
readOnly 事务读写性
noRollbackFor 一组异常类,遇到时不回滚。默认为{}。
noRollbackForClassName 一组异常类名,遇到时不回滚,默认为{}
rollbackFor 一组异常类,遇到时回滚
rollbackForClassName 一组异常类名,遇到时回滚
timeout 超时时间,以秒为单位
value 可选的限定描述符,指定使用的事务管理器

isolation

isolation属性可配置的值有:

  • Isolation.READ_COMMITTED :使用各个数据库默认的隔离级别
  • Isolation.READ_UNCOMMITTED :读未提交数据(会出现脏读,不可重复读,幻读)
  • Isolation.READ_COMMITTED :读已提交的数据(会出现不可重复读,幻读)
  • Isolation.REPEATABLE_READ :可重复读(会出现幻读)
  • Isolation.SERIALIZABLE :串行化
数据库默认隔离级别
  • MYSQL: 默认为REPEATABLE_READ级别
  • SQLSERVER: 默认为READ_COMMITTED
  • Oracle 默认隔离级别 READ_COMMITTED

propagation

propagation属性可配置的值有:

传播机制 说明
Propagation.REQUIRED 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是 最常见的选择,也是Spring的默认传播机制。
Propagation.SUPPORTS 支持当前事务,如果当前没有事务,就以非事务方式执行。
Propagation.MANDATORY 使用当前的事务,如果当前没有事务,就抛出异常。
Propagation.REQUIRES_NEW 新建事务,如果当前存在事务,把当前事务挂起。
Propagation.NOT_SUPPORTED 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
Propagation.NEVER 以非事务方式执行,如果当前存在事务,则抛出异常。
Propagation.NESTED 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与 PROPAGATION_REQUIRED 类似的操作。

上一篇写事务的文章中已经把这两个属性说得很清楚了,这里就不再赘述了。


readOnly

默认情况下是false,可以显示指定为true, 告诉程序该方法下使用的是只读操作,如果进行其他非读操作,则会跑出异常;

  • 概念: 从这一点设置的时间点开始(时间点a)到这个事务结束的过程中,其他事务所提交的数据,该事务将看不见!(查询中不会出现别人在时间点a之后提交的数据)

  • 应用场合: 如果你一次执行单条查询语句,则没有必要启用事务支持,数据库默认支持SQL执行期间的读一致性; 如果你一次执行多条查询语句,例如统计查询,报表查询,在这种场景下,多条查询SQL必须保证整体的读一致性,否则,在前条SQL查询之后,后条SQL查询之前,数据被其他用户改变,则该次整体的统计查询将会出现读数据不一致的状态,此时,应该启用事务支持。

【注意是一次执行多次查询来统计某些信息,这时为了保证数据整体的一致性,要用只读事务】


rollbackForClassName/rollbackFor

Spring默认情况下会对运行期例外(RunTimeException)进行事务回滚。这个例外是unchecked,如果遇到checked意外就不回滚。

用来指明回滚的条件是哪些异常类或者异常类名。


noRollbackForClassName/noRollbackFor

用来指明不回滚的条件是哪些异常类或者异常类名。


timeout

用于设置事务处理的时间长度,阻止可能出现的长时间的阻塞系统或者占用系统资源。单位为秒。如果超时设置事务回滚,并抛出TransactionTimedOutException异常。


value

value这里主要用来指定不同的事务管理器;主要用来满足在同一个系统中,存在不同的事务管理器。

比如在Spring中,声明了两种事务管理器txManager1, txManager2.然后,用户可以根据这个参数来根据需要指定特定的txManager.

  • 存在多个事务管理器的情况: 在一个系统中,需要访问多个数据源,则必然会配置多个事务管理器。

注意点

  • 不要在接口上使用注解,可能会无效,要在实现类的具体方法上写。
  • 可以在类级别上使用注释,但是会使类下的所有方法都有事务,影响效率。
  • 只能对public方法使用事务,@Transactional注解的方法都是被外部其他类调用才有效,故只能是public。
  • 使用了@Transactional的方法,对同一个类里面的方法调用, @Transactional无效。比如有一个类Test,它的一个方法A,A再调用Test本类的方法B(不管B是否public还是private),但A没有声明注解事务,而B有。则外部调用A之后,B的事务是不会起作用的。

不回滚的情况

spring事务机制:

  • 默认spring事务只在发生未被捕获的RuntimeException时才回滚。
  • spring aop异常捕获原理:被拦截的方法需要显式抛出异常,不能经过处理,这样aop代理才能捕获到方法的异常,才能进行回滚。

    默认情况下aop只捕获RuntimeException的异常,但可以通过配置来捕获特定的异常并回滚。换句话说,在service的方法中不使用try-catch或者在catch中最后加上throw new RuntimeException(),这样程序发生异常时才能被aop捕获进而回滚。

  • 解决方案:

    • 例如Service层处理事务,那么Service中的方法中不做异常捕获,或者在catch语句中最后增加throw new RuntimeException()语句,以便aop捕获异常再去回滚,并且在service上层要继续捕获这个异常并处理。

    • 在service层方法的catch语句中增加:TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();语句,手动回滚,这样上层就无需去处理异常。

    • SQLException本身是受检查的异常,但是在Spring框架下,抛出的是org.springframework.dao包中的异常,都是RuntimeException的子类,这是有关数据库的异常会回滚的原因。


例子

不想写了…