首页 > 其他分享 >面试突击85:为什么事务@Transactional会失效?

面试突击85:为什么事务@Transactional会失效?

时间:2022-09-20 20:44:37浏览次数:90  
标签:事务 Transactional 回滚 try 面试 catch public 85

导致 @Transactional 失效的常见场景有以下 5 个:

  1. 非 public 修饰的方法;
  2. timeout 超时时间设置过小;
  3. 代码中使用 try/catch 处理异常;
  4. 调用类内部的 @Transactional 方法;
  5. 数据库不支持事务。

很多人只知道答案但不知道原因,这就像只谈恋爱不结婚一样,是不能让人接受的,所以本篇我们就来讨论一下,导致事务失效的背后原因到底是啥?

在以上 5 种场景中,第 2 种(timeout 超时时间设置过小)和第 5 种(数据库不支持事务)很好理解,我们这里就不赘述了,本文我们重点来讨论其他 3 种情况。

1.非 public 修饰的方法

非 public 修饰的方法上,即使加了 @Transactional 事务依然不会生效,原因是因为 @Transactional 使用的是 Spring AOP 实现的,而 Spring AOP 是通过动态代理实现的,而 @Transactional 在生成代理时会判断,如果方法为非 public 修饰的方法,则不生成代理对象,这样也就没办法自动执行事务了,它的部分实现源码如下:

protected TransactionAttribute computeTransactionAttribute(Method method, Class<?> targetClass) {
   // Don't allow no-public methods as required.
   // 非 public 方法,设置为 null
   if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
      return null;
   }
   // 后面代码省略....
 }

2.try/catch 导致事务失效

@Transactional 执行流程是: @Transactional 会在方法执行前,会自动开启事务;在方法成功执行完,会自动提交事务;如果方法在执行期间,出现了异常,那么它会自动回滚事务。

然而如果在方法中自行添加了 try/catch 之后,事务就不会自动回滚了,这是怎么回事呢?
造成这个问题的主要原因和 @Transactional 注解的实现有关,它的部分实现源码如下:

protected Object invokeWithinTransaction(Method method, Class<?> targetClass, final InvocationCallback invocation)
      throws Throwable {
   // If the transaction attribute is null, the method is non-transactional.
   final TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass);
   final PlatformTransactionManager tm = determineTransactionManager(txAttr);
   final String joinpointIdentification = methodIdentification(method, targetClass);

   if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
       // Standard transaction demarcation with getTransaction and commit/rollback calls.
       // 自动开启事务
      TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
      Object retVal = null;
      try {
         // This is an around advice: Invoke the next interceptor in the chain.
         // This will normally result in a target object being invoked.
         // 反射调用业务方法
         retVal = invocation.proceedWithInvocation();
      }
      catch (Throwable ex) {
          // target invocation exception
          // 异常时,在 catch 逻辑中,自动回滚事务
         completeTransactionAfterThrowing(txInfo, ex);
         throw ex;
      }
      finally {
         cleanupTransactionInfo(txInfo);
      }
       // 自动提交事务
      commitTransactionAfterReturning(txInfo);
      return retVal;
   }

   else {
     // .....
   }
}

从上述实现源码我们可以看出:当执行的方法中出现了异常,@Transactional 才能感知到,然后再执行事务回滚,而当开发者自行添加了 try/catch 之后,@Transactional 就感知不到异常了,从而就不会触发事务的自动回滚了,这就是为什么当 @Transactional 遇到 try/catch 之后就不会自动回滚(事务)的原因。

3.调用类内用的 @Transactional 方法

当调用类内部的 @Transactional 修饰的方法时,事务也不会生效,如下代码所示:

@RequestMapping("/save")
public int saveMappping(UserInfo userInfo) {
    return save(userInfo);
}
@Transactional
public int save(UserInfo userInfo) {
    // 非空效验
    if (userInfo == null ||
        !StringUtils.hasLength(userInfo.getUsername()) ||
        !StringUtils.hasLength(userInfo.getPassword()))
        return 0;
    int result = userService.save(userInfo);
    int num = 10 / 0; // 此处设置一个异常
    return result;
}

上述代码在添加用户之后即使遇到了异常,程序也没有执行回滚,这是因为 @Transactional 是基于 Spring AOP 实现的,而 Spring AOP 又是基于动态代理实现的,而当调用类内部的方法时,不是通过代理对象完成的,而是通过 this 对象实现的,这样就绕过了代理对象,从而事务就失效了。

总结

非 public 修饰的方法在 @Transactional 实现时做了判断,如果是非 public 则不会生成代理对象,所以事务就失效了;而调用类内部的 @Transactional 修饰的方法时,也是因为没有成功调用代理对象,是通过 this 来调用方法的,所以事务也失效了;@Transactional 在遇到开发者自定义的 try/catch 也会失效,这是因为 @Transactional 只有感知到了异常才会自动回滚(事务),但如果用户自定义了 try/catch,那么 @Transactional 就感知不到异常,所以也就不会自动回滚事务了。

参考 & 鸣谢

blog.csdn.net/qq_20597727/article/details/84900994

是非审之于己,毁誉听之于人,得失安之于数。

公众号:Java面试真题解析

面试合集:https://gitee.com/mydb/interview

标签:事务,Transactional,回滚,try,面试,catch,public,85
From: https://www.cnblogs.com/vipstone/p/16712467.html

相关文章

  • MySQL-面试题总结
    1.为什么InnoDB存储引擎选择B+Tree索引结构。(1)思路,为什么不采用二叉树和红黑树?普通二叉树,顺序插入,形成链表,大大影响查询效率。红黑树本质上也是二叉树,大数据量,树的......
  • 前端面试题JavaScript篇——2022-09-20
    每日3题1以下代码执行后,控制台中的输出内容为?//index.jsconsole.log(1);import{sum}from"./sum.js";console.log(sum(1,2));//sum.jsconsole.log(2);exp......
  • 面试--Go函数返回局部变量的指针是否安全
    点击查看代码packagemainfuncadd(x,yint)*int{ res:=0 res=x+y return&res}funcmain(){ add(1,2)}![image](https://img2022.cnblogs.com/b......
  • 软件测试面试过程解析
      对很多没有如何经验或者面试次数不多的小伙伴来说感觉每次跳槽或者首次面试的时候都会感觉特别紧张,然后不知道自己该准备些什么,或者不知道面试的流程是什么样子,那么......
  • 为什么阿里规定需要在事务注解@Transactional中指定rollbackFor?
    阿里巴巴Java规范:方法【edit】需要在Transactional注解指定rollbackFor或者在方法中显示的rollback。异常的分类Throwable:有两个重要的子类:Exception(异常)和Error(错......
  • 前端面试总结02-变量类型和计算
    值类型与引用类型值类型:   引用类型常见值类型:consta//undefinedconsts='abc'constn=100constb=trueconsts=Symbol('s')常见引用类型:constobj={x:......
  • 前端面试总结01-html与css
    html:(1)语义化标签的理解:1.增加代码的可读性2.让搜索引擎更容易读懂(2)块级元素与内联元素的标签与区别1.块状元素:display:block/table;常用标签:div,h1,h2,table,ul......
  • 大数据面试每日一题-Kafka为什么这么快?
    Kafka为什么这么快?1.kafka是基于partition 分区技术实现的,分布式提高Kafka的并发量2.KafKa的稀疏索引机制,kafka每4kb插入一个索引,索引在查询时候,可以比较高的效率查到到......
  • Vue面试题19:在实际开发过程中,你知道的Vue最佳实践有哪些?(总结自B站up主‘前端杨村长’
    思路:查看vue官方文档:风格指南(重点关注AB级)、最佳实践(生产部署、性能、访问、安全)回答范例从编码风格、性能、安全等方面说几条:1.编码风格方面:命名组件时使用“多词"......
  • 9.19面试题
    说说你对MVC的理解?MVC是什么?是一种设计模式,为了解决以往JSP的繁琐开发M(model)V(view)C(controller),其中view处理页面显示,contrller是用来处理用户的交互与事件,mdoel定义实......