目录
1. 导致事物失效的场景有哪些 ?
1.1 为什么 @Transaction 修饰非 public 方法会导致事物失效 ?
1.2 代码中使用 try/catch 处理了异常为什么会导致事物失效 ?
1.3 为什么在类内部调用 @Transaction 修饰的方法会导致事务失效 ?
1.4 数据库不支持事物,导致事物失效
导致事物失效的常见场景有 4 个:
【浅层原因】
@Transaction 注解底层源码限制了必须是 public 方法才能执行后续的流程。
【深层原因】
1.前置知识:
首先,我们得知道 @Transaction 底层是基于动态代理实现的,在执行目标方法之前,代理对象会帮我们开启事务,在执行完目标方法之后,代理对象会帮我们进行提交或回滚事物。其次动态代理的底层只有 JDK Proxy 和 CGLib 两种实现。
2.此时我们再来细究一下这两种代理为什么只能代理被 public 修饰的方法:
① JDK Proxy :要求被代理类一定要实现接口,JDK 生成的代理类也需要实现这个接口(为了保证方法名一致),而接口中的方法的访问控制符都是 public,所以 JDK Proxy 只能代理公共方法。
② CGLib:通过生成被代理类的子类来实现代理,而子类继承父类的方法,只能继承父类中使用 public 和 protected 修饰的方法;又因为 protected 修饰的方法不允许跨包访问非子类方法,所以被代理类的方法访问是受限制的,因此 CGLib 只有代理 public 修饰的方法,它的功能才是完整的。
我们都知道使用 @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();
}
因为 @Transaction 是基于动态代理实现的,而当我们在类内部调用 @Transaction 修饰的方法时,就不是通过代理对象调用的,而是通过 this 对象调用的,这样就绕过了代理对象,从而就导致事物失效了。
最后一条数据库不支持事物,那就不用说了,肯定不生效。