Spring的事务传播等级以及事务失效

是什么

事务其实就是多个人一起去干同一件事情,如果其中一个人出现问题,那么整个事务就会回滚,也就是回退到事务开始前的状态。

首先明确Spring中一共有7种事务传播等级:

  1. REQUIRED(默认)
  2. SUPPORTS
  3. MANDATORY
  4. REQUIRES_NEW
  5. NOT_SUPPORTED
  6. NEVER
  7. NESTED

我们需要去理解然后记忆

记忆方法

可以理解为舍友买饭。
Required就是舍友如果买饭回来了,就跟他一起吃;如果舍友没有买饭就自己去买饭
Support是舍友如果买饭回来了就一起吃,他没买我也不吃了
Mandatory是舍友必须买饭,如果舍友没有买饭我就生气抛异常
Requires_new是不管舍友买没买饭,我都自己买饭
Not_supported是不管舍友买不买饭我都不吃
Never是如果舍友买饭了我就报错
Nested是如果舍友买饭了,我就买点小吃。但是和Required的区别在于,我这个小吃成功失败并不重要,但是如果舍友的饭报错了,我的小吃也会回滚。

但是其实我们没有必要照本宣科,在面试的时候全都讲出来。我们可以根据我们项目的实际情况,挑一两个讲述。

为什么

为了保证业务逻辑的原子性,同时避免事务的过度嵌套。

  • 原子性:多个方法操作(保存订单、更新库存、扣除积分)必须再一个事务边界内,要么全部成功,要么全部失败。
  • 避免过度嵌套:比如一个查询日志的方法,它不需要在事务中运行,他就可以被一个没有事务的方法调用,此时他也不会开启事务,节省资源。而如果它被一个事务性
    性方法调用时,又能自动加入,保证数据一致性。

怎么做

在Spring中,通常使用@Transactional注解来声明事务,在括号内配置事务的属性。如:

// 写法一:使用默认传播行为(推荐,更简洁)
@Transactional
public void placeOrder(Order order) {
// 业务逻辑
orderDao.save(order);
inventoryService.deductStock(order.getItems()); // 这个方法也会加入当前事务
pointsService.updatePoints(order.getUserId(), order.getAmount());
}

// 写法二:显式指定传播行为(更清晰,但冗余)
@Transactional(propagation = Propagation.REQUIRED)
public void placeOrder(Order order) {
// ... 同上
}

拓展问题

事务的失效场景

首先我们要了解,事务的实现基于代理机制工作的。
可以把你的业务方法想象成一个被保护的对象
@Transactional是一个雇佣保镖的订单
Spring框架就是保镖公司
事务的开启和回滚就是保镖在老板出发时跟随(开启事务),遇到危险时带老板撤回起点(回滚)

事务失效的根本原因其实就是这个保镖没跟上老板,自然就失效了。

哪些场景下失效

保镖没跟上(方法非public修饰)

@Transactional注解用在了一个非public修饰的方法上

因为Spring默认使用基于接口的jdk动态代理或基于类的CGLIB代理来创建事务代理对象。对于非public方法,代理对象无法
重写这些方法,因此@Transactional注解会被直接忽略

老板自己单飞(类内部方法调用)

在一个类的内部,一个未标注@Transactional注解的方法直接调用了另一个标注了@Transactional注解的方法

@Service
public class UserService {

public void methodA() {
// ... 一些操作
this.methodB(); // 直接内部调用,事务失效!
}

@Transactional
public void methodB() {
// 数据库操作
}
}

问题在于:A方法内部直接通过this.methodB()调用B方法时,this指向的是目标对象本身,而不是代理对象。所以@Transactional注解完全不会被代理逻辑处理

保镖认错人(异常类型不对或被catch掉)
  1. 异常类型不对:默认情况下Spring事务只有在遇到运行时异常(RuntimeException)和Error时才会回滚,但是如果方法中抛出其他异常,比如Exception
    ,事务则会提交
  2. 异常被捕获:方法内部try-catch自己捕获。如果异常在rollbackFor指定的异常内,且一定要使用try-catch自己处理,需要在catch块中手动回滚:
    TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
保镖公司没开业(未开启事务管理)

Spring boot中通常使用 @EnableTransactionManagement来开启注解事务(自动配置)

如果是纯Spring项目那就要在配置类上添加@EnableTransactionManagement

数据库引擎不支持事务

如果使用的是MySQL的MyISAM存储引擎,那么事务将不起作用。因为MyISAM存储引擎不支持事务。

改为InnoDB存储引擎即可

事务的传播属性设置不当(不多赘述)
方法上用了final和static

对于 CGLIB 代理,它无法代理 final方法。
因为 final方法不能被子类重写。
static方法属于类,不属于实例,代理机制同样无法作用其上。