Spring事务注解@Transactional的一些注意点
在《Spring Boot事务管理(下)》中,已经介绍了如果在 protected、private 或者默认可见性的方法上使用@Transactional,事务将是摆设,也不会抛出任何异常,并简单的给出了一些使用时的注意事项。本文在此基础上进一步解释如何正确使用Spring声明式事务。
一、关于目标对象内部方法自我调用的不同情形和存在的问题
1、情形1:只给b方法上加事务注解,a方法上不加
public interface AService { public void a(); public void b(); } @Service() public class AServiceImpl implements AService{ public void a() { this.b(); } @Transactional(rollbackFor={Exception.class}) public void b() { insert(); update(); } }
只要给目标类AServiceImpl的某个方法加上注解@Transactional,Spring就会为目标类生成对应的代理类,以后调用AServiceImpl中的所有方法都会先走代理类(即使调用未加事务注解的方法a,也走),即在通过getBean("AServiceImpl")获得业务类时,实际上得到的是一个代理类,假设这个类叫做AServiceImplProxy,Spring为AServiceImpl生成的代理类类似于如下代码:
public class AServiceImplProxy implements AService{ public void a() { //反射调用目标类的a方法 } public void b() { //启动事务的代码 //反射调用目标类的b方法 //事务提交的代码 } }
由于目标类中只有b方法加入了事务管理,所以代理类中只为b方法加入了横切事务逻辑,Spring事务管理的本质是通过aop为目标类生成动态代理类,并在需要进行事务管理的方法中加入事务管理的横切逻辑代码,如AServiceImplProxy中的b方法所示。
![](https://img2020.cnblogs.com/blog/1208468/202108/1208468-20210828164201548-1863746784.png)
![](https://img2020.cnblogs.com/blog/1208468/202108/1208468-20210828164201627-1667094694.png)
2、情形2:给a方法加事务注解,b方法上加或不加
@Service() public class AServiceImpl implements AService{ @Transactional(rollbackFor={Exception.class}) public void a() { this.b(); } @Transactional(rollbackFor={Exception.class}) public void b() { insert(); update(); } }
此时生成的代理类类似如下代码:
public class AServiceImplProxy implements AService{ public void a() { //启动事务的代码 //反射调用目标类的a方法 //事务提交的代码 } public void b() { //启动事务的代码 //反射调用目标类的b方法 //事务提交的代码 } }
即为a和b都加入了事务横切逻辑。在这种情况下,调用顺序还和1中情形类似,区别在于在反射调用目标对象的a方法前,会对a方法开启事务管理,虽然调用的b方法还是目标对象中没有加事务逻辑的代码,spring却会把b合并到a的事务中去,此时相当于只有一个事务。如果再将目标类代码改为:
@Service() public class AServiceImpl implements AService{ @Transactional(rollbackFor={Exception.class}) public void a() { this.b(); } public void b() { insert(); update(); } }
即只在a上加事务控制,由于b会合并到a的事务中,所以b中的逻辑也可以被事务管理。
@Transactional(rollbackFor={Exception.class}) public void a() { ((AService) AopContext.currentProxy()).b(); //即调用AOP代理对象的b方法即可执行事务切面进行事务增强 }
这时,就会强制要求调用代理类中的b方法,从而开启b上的事务,此时b事务上标注的事务传递规则也就可以生效了,详情参见:http://jinnianshilongnian.iteye.com/blog/1487235
二、try catch的问题
public interface AService { public void a(); } @Service() public class AServiceImpl implements AService{ @Transactional(rollbackFor={Exception.class}) public void a() { try{ insert(); update(); }catch(Exception e){ // to do something } } }
自己在代码中显式捕获异常会导致spring事务回滚失效,原因:spring事务是通过aop捕获到异常后再执行回滚,如果业务代码中显式捕获了异常,会导致spring捕获不到,回滚自然失败。
public void a() throws Exception{ try{ insert(); update(); }catch(Exception e){ throw new Exception(e); } }
不足是本方法的调用端也必须显式捕获异常。
public void a() { try{ insert(); update(); }catch(Exception e){ //显式回滚 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); } }
不足是事务控制代码会侵入业务代码,也正是因为编程式事务管理会侵入业务逻辑代码,所以才有了申明式事务管理。
public interface BService { public void a(); public void b(); } @Service() public class BServiceImpl implements BService{ @Transactional(rollbackFor={Exception.class}) public void b() { insert(); update(); } }
相应的调用端a方法中变为:
public void a() throws Exception{ try{ bService.b(); }catch(Exception e){ throw new Exception(e); } }
Reference
![](https://common.cnblogs.com/images/wechat.png)