写点什么

我的事务为什么会失效

用户头像
JFound
关注
发布于: 2020 年 05 月 21 日
我的事务为什么会失效

我的事务为啥会失效?


在用 Spring 的时候,我们经常用过使用 @Transactional 声明式事务,但是有些时候,@Transactional 声明的事务却是没有生效。


一个例子。


  • 环境


数据库为 innodb,代码为基于 spring 的一个 demo。


Demo 代码如下:


package jfound.demo;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import org.springframework.transaction.annotation.Propagation;import org.springframework.transaction.annotation.Transactional;
@Servicepublic class DemoService { @Autowired private DemoMapper demoMapper; public void methodA() { this.methodB(i); } @Transactional public void methodB() {//发生异常的方法B User user = new User(); user.setId(1); user.setName("Tester"); user.setAge(10); demoMapper.insertUser(user);//往user表插入一条用户记录 int a = 1 / 0;//抛出ArithmeticException异常 }}
复制代码


  • 代码说明


代码中methodB声明式事务注解 @Transactional,在 B 中,插入一个user对象后,再伪造一个 ArithmeticException 异常然后抛出;而methodA中的方法是没有声明式事务的,methodA调用了方法methodB


  • 执行例子


注:调用方都是 DemoService 外的 Component,如 controller、其他 service 等。


demoService.methodA();
复制代码


当外部一个 bean 调用方法 methodA 时候,methodA 会接收到一个异常,但 user 已经能成功插入数据库,也就间接说明事务是没有回滚的。有人说在抛异常的时候,methodA没有对methodB进行异常捕捉并手动回滚,然后试下手动回滚。


public void methodA() {	try{		this.methodB(i);	}catch(Exception e){    e.printStackTrace();    TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();  }    }
复制代码


当添加TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();手动回滚的时候,返回methodA中抛出了一下异常:


org.springframework.transaction.NoTransactionException: No transaction aspect-managed TransactionStatus in scope    at org.springframework.transaction.interceptor.TransactionAspectSupport.currentTransactionStatus(TransactionAspectSupport.java:122)    ...
复制代码


从抛出的异常描述来看,也就是没有异常,怎么回事?明明是有 @Transactional 注解声明了事务的。


当调用方直接调用 methodB 的时候。


demoService.methodB();
复制代码


虽然调用方接收到了异常,但是 user 却没有插入到数据库,也就是说明,事务是生效的。


@Transactional 工作原理


  • @Transactional 被外部类的代码调用时,Spring 在运行时为方法所在类生产一个 AOP 对象

  • AOP 对象根据*@Transactional*的属性,决定是否由事务拦截器对此方法进行事务拦截

  • 在进行事务拦截时,会先开始事务,然后执行业务代码,根据执行结果是否抛出异常,然后通过事务管理器来进行 rollback 还是*commit*


也可以阅读下上一篇 Spring注入的对象到底是什么类型 来理解下 spring 注入的对象是什么类型的对象。


简单说,spring 的声明式事务是通过aop来管理的,是用动态代理对象来实现事务管理,所以调用方所注入的对象一定是代理对象才能使事务生效,当然,当 spring ioc 检查到需要 aop 的时候,是会生成代理对象。然而需要注意的是,所在类的方法之间调用时,@Transactional 注解是不会生效的。


@Async 等注解也是同样的道理。


解决方法


  • 最外层被调用的声明 @Transactional 注解,例如在例子中的methodA上添加注解 @Transactional

  • 使用 TransactionTemplate 来手动管理实务


注意事项


事务失效问题排查


  • 数据库引擎是否支持事务


Mysql 的 Innodb 和 bdb 引擎是事务引擎,支持事务,如需要事务控制,必须使用事务引擎


  • 声明式注解是否被加载成 bean


@Transactional 注解所在对象必须是被 spring ioc 管理的,也就是一个 bean,自己手动 new 出来的对象没被 spring 管理的话,注解是失效的。


  • 注解所在的方法是被 public 修饰,并非自调


注解所在的方法必须被 public 修饰,而且调用方必须是其他 bean,不能是类本身的其他方法调用


总结


在使用 @Transactional 声明式事务的时候,要注意一定是外部的 bean 来调用,@Transactional 的功能是通过 AOP 动态代理类来实现事务管理功能,类自身调用的没有效果的。


关注我,发现更多Java领域知识


用户头像

JFound

关注

梳理java知识,发现Java相关的更多领域知识 2020.05.19 加入

Java技术栈奋斗者

评论

发布
暂无评论
我的事务为什么会失效