导致事物失效的场景有哪些 ?

目录

1. 导致事物失效的场景有哪些 ?

1.1 为什么 @Transaction 修饰非 public 方法会导致事物失效 ?

1.2 代码中使用 try/catch 处理了异常为什么会导致事物失效 ?

1.3 为什么在类内部调用 @Transaction 修饰的方法会导致事务失效 ?

1.4 数据库不支持事物,导致事物失效


1. 导致事物失效的场景有哪些 ?

导致事物失效的常见场景有 4 个:

  1. @Transaction 修饰非 public 方法;
  2. 代码中使用 try/catch 处理了异常;
  3. 在类内部调用 @Transaction 修饰的方法;
  4. 数据库不支持事物

1.1 为什么 @Transaction 修饰非 public 方法会导致事物失效 ?

【浅层原因】

@Transaction 注解底层源码限制了必须是 public 方法才能执行后续的流程。

导致事物失效的场景有哪些 ?_第1张图片

【深层原因】

1.前置知识:

        首先,我们得知道 @Transaction 底层是基于动态代理实现的,在执行目标方法之前,代理对象会帮我们开启事务,在执行完目标方法之后,代理对象会帮我们进行提交或回滚事物。其次动态代理的底层只有 JDK Proxy CGLib 两种实现。

2.此时我们再来细究一下这两种代理为什么只能代理被 public 修饰的方法:

JDK Proxy :要求被代理类一定要实现接口,JDK 生成的代理类也需要实现这个接口(为了保证方法名一致),而接口中的方法的访问控制符都是 public,所以 JDK Proxy 只能代理公共方法。

② CGLib:通过生成被代理类的子类来实现代理,而子类继承父类的方法,只能继承父类中使用 public 和 protected 修饰的方法又因为 protected 修饰的方法不允许跨包访问非子类方法,所以被代理类的方法访问是受限制的,因此 CGLib 只有代理 public 修饰的方法,它的功能才是完整的。

1.2 代码中使用 try/catch 处理了异常为什么会导致事物失效 ?

我们都知道使用 @Transaction 注解后,当程序抛出异常时,它会自动帮我们回滚。

当我们手动加了 try/catch 之后,为什么事物就不会自动回滚了 ?

这就得分析 @Transaction 的底层源码了:

protected Object invokeWithinTransaction(Method method, Class targetClass, final InvocationCallback invocation)
        throws Throwable {
    final TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass);
    final PlatformTransactionManager tm = determineTransactionManager(txAttr);
    final String joinpointIdentification = methodIdentification(method, targetClass);

    if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
        //开启事务
        TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
        Object retVal = null;
        try {
            //方法调用
            retVal = invocation.proceedWithInvocation();
        } catch (Throwable ex) {
            // target invocation exception
            //回滚事务
            completeTransactionAfterThrowing(txInfo, ex);
            throw ex;
        } finally {
            cleanupTransactionInfo(txInfo);
        }
        //提交事务
        commitTransactionAfterReturning(txInfo);
        return retVal;
    } else {
        // .....
    }
}

        从上述源码,我们就可以看出问题了,当程序抛出异常的时,@Transaction 注解底层会捕获异常并调用回滚方法,而如果我们手动处理了异常,那么 @Transaction 注解就没办法监控到异常了,也就不会执行回滚方法了。

那添加了 try/catch 之后,我们该怎么让事物进行回滚呢 ?

1. 重新抛出异常

try {
    int i = 10 / 0;
} catch(Exception e) {
    // 重新抛出异常
    throw e;
}

2. 手动回滚事物(推荐)

try {
    int i = 10 / 0;
} catch(Exception e) {
    // 手动回滚事物
    TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}

1.3 为什么在类内部调用 @Transaction 修饰的方法会导致事务失效 ?

        因为 @Transaction 是基于动态代理实现的,而当我们在类内部调用 @Transaction 修饰的方法时,就不是通过代理对象调用的,而是通过 this 对象调用的,这样就绕过了代理对象,从而就导致事物失效了。

1.4 数据库不支持事物,导致事物失效

最后一条数据库不支持事物,那就不用说了,肯定不生效。

你可能感兴趣的:(JavaEE进阶,数据库,spring,boot)