面试突击:为什么事务@Transactional会失效?
导致 @Transactional 失效的常见场景有以下 5 个:
- 非 public 修饰的方法。
- timeout 超时时间设置过小。
- 代码中使用 try/catch 处理异常。
- 调用类内部的 @Transactional 方法。
- 数据库不支持事务。
很多人只知道答案但不知道原因,这就像只谈恋爱不结婚一样,是不能让人接受的,所以本篇我们就来讨论一下,导致事务失效的背后原因到底是啥?
在以上 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 就感知不到异常,所以也就不会自动回滚事务了。
- Spring中实现异步调用的方式有哪些?
- 带参数的全类型 Python 装饰器
- 整理了几个Python正则表达式,拿走就能用!
- SOLID:开闭原则Go代码实战
- React中如何引入CSS呢
- 一个新视角:前端框架们都卷错方向了?
- 编码中的Adapter,不仅是一种设计模式,更是一种架构理念与解决方案
- 手写编程语言-递归函数是如何实现的?
- 一文搞懂模糊匹配:定义、过程与技术
- 新来个阿里 P7,仅花 2 小时,做出一个多线程永动任务,看完直接跪了
- Puzzlescript,一种开发H5益智游戏的引擎
- @Autowired和@Resource到底什么区别,你明白了吗?
- CSS transition 小技巧!如何保留 hover 的状态?
- React如此受欢迎离不开这4个主要原则
- LeCun再炮轰Marcus: 他是心理学家,不是搞AI的
- Java保证线程安全的方式有哪些?
- 19个杀手级 JavaScript 单行代码,让你看起来像专业人士
- Python 的"self"参数是什么?
- 别整一坨 CSS 代码了,试试这几个实用函数
- 再有人问你什么是MVCC,就把这篇文章发给他!