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

开发 前端
本篇我们就来讨论一下,导致事务失效的背后原因到底是啥?

导致 @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 就感知不到异常,所以也就不会自动回滚事务了。

责任编辑:姜华 来源: 今日头条
相关推荐

2022-09-14 19:50:22

事务场景流程

2022-04-13 20:53:15

Spring事务管理

2022-06-27 07:23:44

MySQL常量优化

2022-01-18 06:59:50

HashMap循环底层

2015-07-10 09:28:09

事务性能

2023-09-27 16:22:51

SpringMySQL原子性

2022-07-27 07:36:01

TCP可靠性

2022-07-13 07:06:47

HTTPSHTTP协议

2023-05-05 07:39:04

Spring事务面试

2023-09-28 09:07:54

注解失效场景

2022-01-24 07:01:20

安全多线程版本

2022-07-25 07:07:35

TCP客户端服务器

2021-12-13 11:12:41

Spring事务失效

2022-03-02 07:36:37

池化技术Java线程池

2022-09-19 06:16:23

事务隔离级别Spring

2022-09-25 22:12:07

事务SpringBoot

2024-05-14 08:37:34

2022-10-09 20:52:19

事务隔离级别传播机制

2022-09-12 22:27:05

编程式事务声明式事务对象

2022-08-09 09:34:32

Spring开发
点赞
收藏

51CTO技术栈公众号