Spring 事务管理@Transactional使用注意事项

事务管理是应用系统开发中必不可少的一部分。Spring 为事务管理提供了丰富的功能支持。Spring 事务管理分为编码式和声明式的两种方式。编程式事务指的是通过编码方式实现事务;声明式事务基于 AOP,将具体业务逻辑与事务处理解耦。声明式事务管理使业务代码逻辑不受污染, 因此在实际使用中声明式事务用的比较多。声明式事务有两种方式,一种是在配置文件(xml)中做相关的事务规则声明,另一种是基于@Transactional注解的方式。

正确设置@Transaction的Propagation属性

  • REQUIRED:业务方法需要在一个容器里运行。如果方法运行时,已经处在一个事务中,那么加入到这个事务,否则自己新建一个新的事务。

  • NOT_SUPPORTED:声明方法不需要事务。如果方法没有关联到一个事务,容器不会为他开启事务,如果方法在一个事务中被调用,该事务会被挂起,调用结束后,原先的事务会恢复执行。

  • REQUIRES_NEW:不管是否存在事务,该方法总汇为自己发起一个新的事务。如果方法已经运行在一个事务中,则原有事务挂起,新的事务被创建。

  • MANDATORY:该方法只能在一个已经存在的事务中执行,业务方法不能发起自己的事务。如果在没有事务的环境下被调用,容器抛出异常。

  • SUPPORTS:该方法在某个事务范围内被调用,则方法成为该事务的一部分。如果方法在该事务范围外被调用,该方法就在没有事务的环境下执行。

  • NEVER:该方法绝对不能在事务范围内执行。如果在就抛异常。只有该方法没有关联到任何事务,才正常执行。

  • NESTED:如果一个活动的事务存在,则运行在一个嵌套的事务中。如果没有活动事务,则按REQUIRED属性执行。它使用了一个单独的事务,这个事务拥有多个可以回滚的保存点。内部事务的回滚不会对外部事务造成影响。它只对DataSourceTransactionManager事务管理器起效。

NOT_SUPPORTEDNEVERSUPPORTS这三个属性值是不要求必须有事务的。

正确的设置@Transactional 的 rollbackFor 属性

默认情况下,如果在事务中抛出了未检查异常(继承自 RuntimeException的异常)或者 Error,则 Spring 将回滚事务;除此之外,Spring 不会回滚事务。

如果在事务中抛出其他类型的异常,并期望 Spring 能够回滚事务,可以指定 rollbackFor。例:

@Transactional(propagation= Propagation.REQUIRED,rollbackFor= MyException.class)

若在目标方法中抛出的异常是 rollbackFor指定的异常的子类,事务同样会回滚。

@Transactional 只能应用到 public 方法才有效

只有@Transactional 注解应用到 public 方法,才能进行事务管理。

这是因为Spring中的事务管理是通过AOP实现的,而Spring实现AOP的方式有基于JDK的动态代理和基于CGLIB的代理两种方式。JDK动态代理是基于接口的,而CGLIB是基于子类的代理。所以,如果方法是private的,那么接口中肯定不会有该方法(接口中的方法默认都是abstract的,static和final修饰都不行),也就无法进行JDK的动态代理增强。同样,private的方法不能在子类中重写,CGLIB代理也无法增强private的方法,final和static修饰的方法也不行。

避免 Spring 的 AOP 的自调用问题

在Spring AOP中,只有当外部调用目标方法时,目标方法才会由Spring生成的代理对象管理。若在同一个类中一个没有@Transaction注解的方法调用另一个有@Transaction注解的方法,有@Transaction注解方法的事务将会被忽略。

一个有@Transaction注解的方法调用另一个有@Transaction注解的方法时,需要确保它们的rollback属性一致,否则发生异常时事务不一定回滚。

@Transaction 配置属性一致时自调用的几种情况:

  • 都有事务注解,异常在子方法出现,事务生效

  • 都有事务注解,异常在主方法出现,事务生效

  • 只有主方法有事务注解,异常在子方法出现,事务生效

  • 只有主方法有事务注解,异常在主方法出现,事务生效

  • 只有子方法有事务注解,异常在子方法出现,事务不生效