百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 文章教程 > 正文

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

xsobi 2024-12-14 15:45 1 浏览

导致 @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 修饰的方法,则不生成代理对象,这样也就没办法自动执行事务了,它的部分实现源码如下:

Bash
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 注解的实现有关,它的部分实现源码如下:

Bash
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

相关推荐

图解面试题:SQL存储过程有什么用?

面试中有时候会问:存储过程有什么用?看了今天的知识,你就知道如何回答了。1.存储过程是什么?假如你每天要开车完成一些列重复的操作:车钥匙启动车,倒车。现在出现了一款新车,可以自动的完成这些重复的工作。...

零基础入门开始学习SQL Server存储过程

你好啊,我是晨希,今天我们来了解一下SQLServer的存储过程,通过这篇零基础的SQLServer存储过程入门指南文章。您将了解到什么是存储过程,如何创建、调用和优化它们,以及如何提高安全性和性...

SQLSERVER:存储过程和函数

在SQLServer中,存储过程和函数是数据库编程的基础。它们允许开发者编写SQL脚本来执行复杂的操作,同时提供了代码重用和逻辑封装的能力。下面将通过一些实例来详细介绍存储过程和函数的使用。...

数据库基础知识:SQL Server存储过程入门必知

什么是存储过程SQL语句需要先编译然后执行,而存储过程(StoredProcedure)是一组为了完成特定功能的SQL语句集,经编译后存储在数据库中,用户通过指定存储过程的名字并给定参数(如果该存储...

SQL储存过程

存储过程是一组预编译的SQL语句,可以保存在数据库中,并作为一个单元执行。它封装了复杂的操作逻辑,可以包含控制结构(如条件判断、循环)、输入参数、输出参数以及错误处理逻辑。存储过程的主要目的是提...

sqlserver开窗及去重row_number() over(partition by c1 order by c2)

开窗函数/分析函数:over()开窗函数也叫分析函数,有两类:一类是聚合开窗函数,一类是排序开窗函数。...

SQL server中exists用法

1、简介?不相关子查询:子查询的查询条件不依赖于父查询的称为不相关子查询?相关子查询:子查询的查询条件依赖于外层父查询的某个属性值的称为相关子查询。带Exists的子查询就是相关子查询...

SQL 将两张包含相同字段和不同字段的表数据合并成一张表

第一种:两个表的相同字段数据查询后合并起来select工号,卡号,姓名,时间from(selectPeople_id工号,People_id卡号,People_name姓名,Rep...

「Oracle」 sql语句查询报错ORA-00904

Oracle报错ORA-00904:标识符无效一般情况下,标识符错误是因为:语句中的列名在表中不存在,修改sql语句或者修改列名即可。...

SQL轻松入门(5):窗口函数

01前言标题中有2个字让我在初次接触窗口函数时,真真切切明白了何谓”高级”?说来也是一番辛酸史!话说,我见识了窗口函数的强大后,便磨拳擦掌的要试验一番,结果在查询中输入语句,返回的结果却是报错,Wh...

SQLServer 日期函数大全

一、统计语句1、--统计当前【>当天00点以后的数据】SELECT*FROM表WHERECONVERT(Nvarchar,dateandtime,111)=CONVERT(Nv...

一文讲懂SQL联合查询UNION

大家好,我是宁一。今天讲解SQL教程第13课:UNION联合查询。...

SQL中的INSERT INTO SELECT语句:数据复制的高效利器

SQL是数据库操作的重要语言,INSERTINTOSELECT语句则是其中的一把利器。本文将详细介绍这一语句的用法和优势,帮助读者更好地理解和运用。一、引言...

SQL server查询-日期操作

常用的sql语句查询:...

sql查询更新update select

针对一个上线的项目进行数据库优化,以便后期统计,遇到一个数据填充的问题,在此记录一下,各位如果也有这种问题,欢迎一起交流。表结构:...