导致 @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);}@Transactionalpublic 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