Spring 避坑指南:Spring 声明式事务 @Transactional 避坑
简介
Spring 支持两种使用事务的方式:声明式和编程式。声明式事务是大多数程序员使用的,一个注解 @Transactional 走天下,由于事务的特性及事务是由 aop 技术来实现的,往往会碰到一些坑,使得事务失效或性能受损,甚至发生死锁现象。
事务失效的坑:AOP 技术限制引起的
Spring 中的事务是 AOP 实现的,Srping AOP 使用 JDK 动态代理或 CGLIB 来创建代理对象。
默认情况下,如果需要代理的对象实现了接口,则使用 JDK 动态代理,否则使用 CGLIB。
可以参考文档:
https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop-proxying
或源码:
由于 JDK 动态代理或 CGLIB 来创建代理技术限制,某些方法或行为不能创建代理行为或自动使用代理对象调用方法,会使得事务失效。
1、final 方法添加 @Transactional,事务不生效;
2、static 方法添加 @Transactional,事务不生效;
3、非 public 方法添加 @Transactional,事务不生效;
可以通过 class-based proxies or consider using AspectJ compile-time or load-time weaving 解决。
4、同一个类的带有事务注解 @Transactional 的两个方法 self-invocation 行为,事务不生效;
jdk 动态代理技术肯定失效,可以通过 CGLIB 技术规避。
事务的坑:Spring 实现机制引起的
1、抛出受检异常 Exception 无法回滚
默认情况下,只有非受检异常 RuntimeException、Error 发生时,事务才会回滚。受检异常 Exception 发生时不会回滚。
图片来源:https://javadevcentral.com/checked-and-unchecked-exception-in-java
事务回滚源码:
默认情况下,非受检异常会回滚:
我们可以设置回滚 Exception 异常类型,来解决受检异常不回滚的问题:
处理逻辑如下:
2、子事务出异常回滚当前事务,导致父方法也无法提交事务
事务的默认传播行为为 Propagation.REQUIRED,子事务可以设置 Propagation.REQUIRES_NEW,在独立事务中执行。
3、方法内 try catch 异常,不再抛给事务框架,不会回滚事务
自己吞掉了异常,Spring 框架不会探测到异常。
4、事务多个业务有异步执行,异常不抛出,事务不会回滚
事务的实现涉及到 java 的 ThreadLocal 特性,如果异步执行,事务信息丢失或异常丢失,导致事务执行或回滚。
5、一个事务中多个业务有同步或异步执行,使用不同的数据源,事务不会生效
使用 spring 的本地事务,同一个事务内必须一个数据源,不能跨数据源,否则必须使用分布式事务。
6、事务所在的类不是 spring 容器管理的
7、未配置事务管理器
8、其他 aop 顺序问题,并且吞并异常,事务失效
事务 aop 有自己默认的顺序:
如果其他开发者或者框架引入的 aop 顺序和事务的顺序相同,由于 Spring 框架 aop 排序问题,很可能导致一些问题的发生。
事务的坑:数据库引起的
1、数据库引擎不支持事务
事务的坑:大事务引发问题
1、锁定数据太多,容易造成大量阻塞或死锁问题和锁等待时间长而引发的锁超时问题;
2、回滚记录占用大量存储空间,事务回滚时间长;
3、并发情况下数据库连接处被占满;
4、事务执行时间长,事务结束后才写入 binlog,容易造成数据库主从延迟
如何避免大事务:
1、不要一股脑的用 @Transactional 注解;
2、大事务拆分为独立的小事务;
3、事务避免 PRC 调用-分布式事务;
4、事务中避免一次处理太多的数据;
5、能不用事务就不用;
小结
版权声明: 本文为 InfoQ 作者【崔认知】的原创文章。
原文链接:【http://xie.infoq.cn/article/8cbe43854c1f040fc5f5c6ec8】。文章转载请联系作者。
评论