Spring的事务传播等级以及事务失效
Spring的事务传播等级以及事务失效
是什么
事务其实就是多个人一起去干同一件事情,如果其中一个人出现问题,那么整个事务就会回滚,也就是回退到事务开始前的状态。
首先明确Spring中一共有7种事务传播等级:
- REQUIRED(默认)
- SUPPORTS
- MANDATORY
- REQUIRES_NEW
- NOT_SUPPORTED
- NEVER
- NESTED
我们需要去理解然后记忆
记忆方法
可以理解为舍友买饭。
Required就是舍友如果买饭回来了,就跟他一起吃;如果舍友没有买饭就自己去买饭
Support是舍友如果买饭回来了就一起吃,他没买我也不吃了
Mandatory是舍友必须买饭,如果舍友没有买饭我就生气抛异常
Requires_new是不管舍友买没买饭,我都自己买饭
Not_supported是不管舍友买不买饭我都不吃
Never是如果舍友买饭了我就报错
Nested是如果舍友买饭了,我就买点小吃。但是和Required的区别在于,我这个小吃成功失败并不重要,但是如果舍友的饭报错了,我的小吃也会回滚。
但是其实我们没有必要照本宣科,在面试的时候全都讲出来。我们可以根据我们项目的实际情况,挑一两个讲述。
为什么
为了保证业务逻辑的原子性,同时避免事务的过度嵌套。
- 原子性:多个方法操作(保存订单、更新库存、扣除积分)必须再一个事务边界内,要么全部成功,要么全部失败。
- 避免过度嵌套:比如一个查询日志的方法,它不需要在事务中运行,他就可以被一个没有事务的方法调用,此时他也不会开启事务,节省资源。而如果它被一个事务性
性方法调用时,又能自动加入,保证数据一致性。
怎么做
在Spring中,通常使用@Transactional注解来声明事务,在括号内配置事务的属性。如:
// 写法一:使用默认传播行为(推荐,更简洁) |
拓展问题
事务的失效场景
首先我们要了解,事务的实现基于代理机制工作的。
可以把你的业务方法想象成一个被保护的对象
@Transactional是一个雇佣保镖的订单
Spring框架就是保镖公司
事务的开启和回滚就是保镖在老板出发时跟随(开启事务),遇到危险时带老板撤回起点(回滚)
事务失效的根本原因其实就是这个保镖没跟上老板,自然就失效了。
哪些场景下失效
保镖没跟上(方法非public修饰)
@Transactional注解用在了一个非public修饰的方法上
因为Spring默认使用基于接口的jdk动态代理或基于类的CGLIB代理来创建事务代理对象。对于非public方法,代理对象无法
重写这些方法,因此@Transactional注解会被直接忽略
老板自己单飞(类内部方法调用)
在一个类的内部,一个未标注@Transactional注解的方法直接调用了另一个标注了@Transactional注解的方法
|
问题在于:A方法内部直接通过this.methodB()调用B方法时,this指向的是目标对象本身,而不是代理对象。所以@Transactional注解完全不会被代理逻辑处理
保镖认错人(异常类型不对或被catch掉)
- 异常类型不对:默认情况下Spring事务只有在遇到运行时异常(RuntimeException)和Error时才会回滚,但是如果方法中抛出其他异常,比如Exception
,事务则会提交 - 异常被捕获:方法内部try-catch自己捕获。如果异常在rollbackFor指定的异常内,且一定要使用try-catch自己处理,需要在catch块中手动回滚:
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
保镖公司没开业(未开启事务管理)
Spring boot中通常使用 @EnableTransactionManagement来开启注解事务(自动配置)
如果是纯Spring项目那就要在配置类上添加@EnableTransactionManagement
数据库引擎不支持事务
如果使用的是MySQL的MyISAM存储引擎,那么事务将不起作用。因为MyISAM存储引擎不支持事务。
改为InnoDB存储引擎即可
事务的传播属性设置不当(不多赘述)
方法上用了final和static
对于 CGLIB 代理,它无法代理 final方法。
因为 final方法不能被子类重写。
static方法属于类,不属于实例,代理机制同样无法作用其上。
