【Spring事务传播机制理解】

1. 什么是事务?

数据库事务是简单的来说就是把几个不同的SQL语句当做一个整体来进行操作,要么全部成功,要么全部失败。它有四种特性:原子性,一致性,隔离性和持久性。
而Spring的事务是封装在数据库事务之上的一种事务处理机制,它有两种管理方式:编程式事务和声明式事务。在平时的使用中,我们大多使用使用的是声明式事务来管理,这也是Spring推荐我们使用的方式。

在今天的工作中,遇到了事务的传播机制。对于这方面的内容之前一直没有能够理解。这里在进行查缺补漏,深入的学习一下。

2. 事务的传播机制

Spring中的事务传播机制有其中:

  • REQUIRED
  • REQUIRED_NEW
  • NESTED
  • SUPPORTS
  • NOT_SUPPORTED
  • MANDATORY
  • NEVER

网上很多的介绍是比较书面性的,也导致了自己的最初在接触这块的时候,不能够很好的理解。今天在业务中遇到了这方便的问题,然后在回头来看这些东西的时候,才感觉有些理解。下面我就按照自己的话,来说说自己对于事务传播机制的理解。

事务的传播:什么是事务的传播?在之前的学习中,我一直没有搞明白,事务还能传播。大家可以结合我今天遇到的问题来看:最近在做公司的仓储管理系统,在其中有这样的一个功能。首先商品需要入库,商品入库之后在库存这方便是分为两个部分来进行区分的,一个是仓库本身的库存(整个仓库有多少库存),一个是针对用户的库存(用户租用公司的仓库,用户自己本身也需要维护自己的库存变化的)。在这种情况下一个商品入库,我需要做的就是把这个商品即添加到整个仓库的库存中,也要添加到用户的库存中,这是两个不同的操作,但是两者之间也才存着联系,所有用户的库存全部加起来的等于仓库总的库存。

下面写一下简单的代码逻辑:

//商品入库
@Transactional(rollbackFor = Exception.class)
public ResponseResult productStockIn(){
//1. 商品入库,商品添加到对应仓库的分区中。
wareHouseStockIn();
//2. 入库的商品属于哪一个用户,用户的库存也要跟着变化
userStockIn();
}
//仓库添加库存
@Transactional(rollbackFor = Exception.class)
public Boolean wareHouseStockIn(){
	//1.库位库存修改
	
	//2.分区库存修改
	
	//3.仓库库存修改
}
//用户库存添加
@Transactional(rollbackFor = Exception.class)
public Boolean userStockIn(){
 //用户库存修改
 //库存操作日志添加	
}

我们可以看到上面的代码逻辑中,首先在商品入库的方法中需要两步操作,这两步操作原则上来说是一个原子操作,只能同时成功,或者同时失败,否则整个仓储管理的库存中就会出现异常。所以需要添加一个实务操作。

在仓库添加库存这一步中,仓库分为不同的分区,每一个分区又区分为不同的库位。商品真正存放的位置是在库位上。但是库位,分区和仓库这三个不同的层次上有需要各自维护自己的库存。所以,仓库添加库存中的这三步操作,原则上也是一个原子操作,所以也需要添加事务。

在用户添加库存这一步中,用户添加库存和添加库存操作记录这两步原则上也是一个原子操作。也需要添加事务。
上述代码中还需要添加锁,保证程序的并发安全,这部分的功能放到后面进行考虑,今天暂且不提。

还有一种可能就是说,把上面所有的代码都放在一个方法中,在这个方法之上添加一个事务。这种操作就不存在我们接下来要谈论的事务的传播,但是这种方法编写出的代码的可读性和灵活性就比较差。后面维护起来也是非常的麻烦,不是一个明智之举。

但是拆分成一个个的方法,就出现了一个问题,在商品入库这一步的方法上已经有了一个事务,在仓库添加库存这一步也存在着一个事务,用户添加库存操作这一步也存在一个事务。这是三个不同的事务。最终程序在执行的时候,是什么开始一个事务,又在什么时候进行提交事务呢?这一点不搞明白的话,事务的原子性就没有办法保证。

比如说在第一部,商品入库这一步,开启了事务,在第二步仓库库存修改这一步也开启了一个事务,那此时的事务新开启一个事务?还是没有开启一个事务?此时如果开启了一个事务,那第一个事务是否是否提交了呢?这些都是问题,这些在两个不同的事务中可能出现的问题就是由事务的传播机制来解决的。

所以可以简单的理解为:事务传播机制就是为了解决在两个不同的方法中都存在事务时,或者一个存在事务,一个不存在事务时,怎么保证事务本身需要解决的原子性。

在文章开头所说的七种事务传播机制,每一种就对应了不同的状态。

下面就来仔细说说:

3. 详解事务的传播机制

我们这里借鉴网上的一个例子,感觉说的挺不错的。

3.1 REQUIRED

REQUIRED的意思是必须的,规定的,理想的。这也是Spring默认的事务传播机制。

书面的解释就是,如果当前的方法中不存在事务,就新创一个事务,如果当前的方法中存在事务,就加入到这个事务中。

我第一次看到这句话的时候是挺懵的,反正是有点看不懂。这也是书面语言的通病,高度的概括。简单点来理解就是:有两个方法A和B,并且在A方法中调用B方法。此时,如果A方法不存在事务,B方法存在事务,那么当运行到B方法时,B方法自己开启一个新的事务,B方法运行结束,事务也就提交了。如果A方法存在事务,B方法也存在事务,此时B方法就不需要在开启一个新的事务,而是加入到A方法的事务中。这个时候事务是在A方法中开启的,在B方法运行结束,A方法也运行结束之后,在提交事务,整个过程中只有A方法的事务再起作用,B方法事务没有起到作用。

  1. 调用者有事务
@Service
public class BookServiceImpl implements BookService {

    private final BookMapper bookMapper;
    private final TitleService titleService;

    @Override
    @Transactional
    public void bookTransaction() {
        String transactionName = TransactionSynchronizationManager.getCurrentTransactionName();
        log.info("调用者中事务:{}", transactionName);
        bookMapper.insert(new Book().setAuthor("zpg"));
        //传播事务
        titleService.titleTransaction();
    }
}
public class TitleServiceImpl implements TitleService {

    private final TitleMapper titleMapper;

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void titleTransaction() {
        String transactionName = TransactionSynchronizationManager.getCurrentTransactionName();
        log.info("被调用者中事务:{}", transactionName);
        titleMapper.insert(new Title().setName("第一章"));
    }
}
// 输出,被调用者使用的是调用者的事务
调用者中事务:com.example.demo.transaction.service.impl.BookServiceImpl.requiredTest
被调用者中事务:com.example.demo.transaction.service.impl.BookServiceImpl.requiredTest

可以看到两个方法中的事务是同一个,并且都是第一个方法的事务。

  1. 调用者无事务
public class BookServiceImpl implements BookService {

    private final BookMapper bookMapper;
    private final TitleService titleService;

    @Override
    public void bookTransaction() {
        String transactionName = TransactionSynchronizationManager.getCurrentTransactionName();
        log.info("调用者中事务:{}", transactionName);
        bookMapper.insert(new Book().setAuthor("zpg"));
        //传播事务
        titleService.titleTransaction();
    }
}
public class TitleServiceImpl implements TitleService {

    private final TitleMapper titleMapper;

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void titleTransaction() {
        String transactionName = TransactionSynchronizationManager.getCurrentTransactionName();
        log.info("被调用者中事务:{}", transactionName);
        titleMapper.insert(new Title().setName("第一章"));
    }
}
// 输出,被调用者创建新事务
调用者中事务:null
被调用者中事务:com.example.demo.transaction.service.impl.TitleServiceImpl.requiredTest

第一个方法没有事务,第二个方法自己新开启了一个新的事务。

3.2 REQUIRES_NEW

REQUIRES_NEW中REQUIRED的意思是必须的,规定的,理想的。NEW的意思是新的。

上面的意思可以简单的理解为必须是一个新的。

也即是事务的传播机制是REQUIRES_NEW的时候,必须创新一个新的事务。例如:在A方法种调用B方法。如果A方法中存在事务,B方法也存在事务。当A方法中的代码执行到B方法的时候,会为B方法新起一个事务,原来A方法的事务挂起(挂起的意思就是A方法的事务放在一边,我(B方法)不使用A的事务,我新建我自己的事务。)A方法的事务和B方法的事务不相干。

当出现异常需要进行回滚时,内部事务方法的回滚不影响外围的事务方法。外围事务方法的回滚也不会影响内部事务,两者时完全独立的,有独立的隔离性。

代码如下:

  1. 调用者有事务
@Override
@Transactional
public void bookTransaction() {
    String transactionName = TransactionSynchronizationManager.getCurrentTransactionName();
    log.info("调用者中事务:{}", transactionName);
    bookMapper.insert(new Book().setAuthor("zpg"));
    //传播事务
    titleService.titleTransaction();
}
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void titleTransaction() {
    String transactionName = TransactionSynchronizationManager.getCurrentTransactionName();
    log.info("被调用者中事务:{}", transactionName);
    titleMapper.insert(new Title().setName("第一章"));
}
// 输出,被调用者和调用者是两个不同事务
调用者中事务:com.example.demo.transaction.service.impl.BookServiceImpl.requiredTest
被调用者中事务:com.example.demo.transaction.service.impl.TitleServiceImpl.requiredTest
  1. 调用者无事务

这种情况下B方法肯定会创建一个新的事务,和REQUIRED中的调用者无事务的情况时一样的。这里就不再赘述。

但是在事务的传播机制为REQUIRED_NEW中,会出现一中特殊的情况需要注意。

现象描述:
一. 当ServiceA.a()方法调用ServiceB.b()方法时,内层事务提交和回滚,都不受外层事务提交或回滚影响。
二. 当ServiceA.a()方法调用ServiceA.b()方法时,内层事务不能正确的提交或回滚。(也就是说内层事务的提交和回滚受到了外层事务的影响,两者之间不再是具有独立性了。)

分析和结论:

@Service
public class ReadService{

	@Transactional(rollbackFor = Exception.class)
	public void methodA(){
		methodB();
	}

	@Transactional(rollbackFor = Exception.class,propagation=Propagation.REQUIRES_NEW)
	public void methodB(){
		//do something
	}
}

在同一个类中方法A中调用方法B时,此时虽然方法B的事务传播机制是REQUIRED_NEW,但是在这种情况下,方法B是不会创建一个新的事务,两个方法使用的是同一个事务。所以才会导致内层事务收到了外层事务的影响。
但是如果方法A和方法B在不同的类中,此时方法A再去调用方法B,事务传播机制REQUIRED_NEW的作用就会体现。
那么为什么Spring中事务传播机制为REQUIRED_NEW只对不同类中的方法生效呢?Debug代码发现跨Service调用方法时,都会但经过org.springframework.aop.framework.CglibAopProxy.DynamicAdvisedInterceptor.intercept()方法,只有经过此处,才能对事务进行控制。

3.3 NESTED

NESTED的意思是嵌套。按照嵌套含义再带入事务中来理解的话,就是两个方法中都存在事务。被调用者方法的事务嵌套在调用者的事务中。这种情况下:调用者的事务是父事务,被调用者的事务是子事务。两者之间虽然是两个不同的事务,但是存在着紧密的联系。

A方法(存在事务)中调用B方法,且B方法的事务传播机制是NESTED的时候。A方法在执行代码时,执行到B的时候会创建一个子事务。
两个事务提交的区别:父事务提交以后,子事务再提交。
两个事务的异常处理区别:子事务异常,父事务不一定回滚。但是父事务异常,则子事务肯定回滚。

  1. 子事务异常
@Override
@Transactional
public void bookTransaction() {
    String transactionName = TransactionSynchronizationManager.getCurrentTransactionName();
    log.info("调用者中事务:{}", transactionName);
    bookMapper.insert(new Book().setAuthor("zpg"));
    try {
        // 传播事务,捕获异常
        titleService.titleTransaction();
    } catch (Exception e) {
        e.printStackTrace();
    }
}
@Override
@Transactional(propagation = Propagation.NESTED)
public void titleTransaction() {
    String transactionName = TransactionSynchronizationManager.getCurrentTransactionName();
    log.info("被调用者中事务:{}", transactionName);
    titleMapper.insert(new Title().setName("第一章"));
    throw new RuntimeException("子事务异常");
}
// 输出,title表插入回滚了,但是book表插入未回滚
调用者中事务:com.example.demo.transaction.service.impl.BookServiceImpl.requiredTest
被调用者中事务:com.example.demo.transaction.service.impl.BookServiceImpl.requiredTest
java.lang.RuntimeException: 子事务异常
  1. 父事务异常
@Override
@Transactional
public void bookTransaction() {
    String transactionName = TransactionSynchronizationManager.getCurrentTransactionName();
    log.info("调用者中事务:{}", transactionName);
    bookMapper.insert(new Book().setAuthor("zpg"));
    //传播事务
    titleService.titleTransaction();
    throw new RuntimeException("父事务异常");
}
@Override
@Transactional(propagation = Propagation.NESTED)
public void titleTransaction() {
    String transactionName = TransactionSynchronizationManager.getCurrentTransactionName();
    log.info("被调用者中事务:{}", transactionName);
    titleMapper.insert(new Title().setName("第一章"));
}
// 输出,book的插入和title的插入都回滚了
调用者中事务:com.example.demo.transaction.service.impl.BookServiceImpl.requiredTest
被调用者中事务:com.example.demo.transaction.service.impl.BookServiceImpl.requiredTest
java.lang.RuntimeException: 父事务异常

3.4 SUPPORTS

SUPPORTS是支持,忍受的意思。

具体的含义就是如果当前方法存在事务,则加入事务。如果当前方法不存在事务,则以非事务方式运行。
这句话的含义有点难以理解,可以参考具体的代码来理解。

@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
   methodB();
   // do something
}
// 事务属性为SUPPORTS
@Transactional(propagation = Propagation.SUPPORTS)
public void methodB() {
    // do something
}
public void methodC(){
	methodB();
   // do something
}

单纯的调用methodB时,methodB方法是按照非事务执行的。
当在methodC中调用methodB时,methodB方法是按照非事务执行的。
当在methodA中调用methodB时,methodB则加入methodA的事务,按照事务执行和REQUIRED的传播机制有点相似。

3.5 NOT_SUPPORTED

NOT_SUPPORTED按照字面意思就好理解。

不支持事务,有事务也是以非事务的方式执行。

这种的传播行为就是,不管咋地都传不到我身上,我就是一直以非事务方式执行。

@Override
@Transactional(rollbackFor=Exception.class)
public void A(Student student){
	String transactionName = TransactionSynchronizationManager.getCurrentTransactionName();
    log.info("调用者中事务:{}", transactionName);
	//方法A,执行新增操作
	student.setName(student.getName+"---A");
	studentDao.add(student);
	//调用B方法
	studentService.B(student);
}
@Override
@Transactional(rollbackFor=Exception.class,propagation=Propagation.NOT_SUPPORTED)
public void B(Student student){
 	String transactionName = TransactionSynchronizationManager.getCurrentTransactionName();
    log.info("被调用者中事务:{}", transactionName);
	student.setName(student.getName+"---B");
	//调用B方法
	studentDao.add(student);
    throw new RuntimeException("B方法抛出了异常");
}
// 输出,虽然被调用者中有事务,也抛出异常,但是数据不会回滚
调用者中事务:com.example.demo.transaction.service.impl.BookServiceImpl.requiredTest
被调用者中事务:com.example.demo.transaction.service.impl.TitleServiceImpl.requiredTest
java.lang.RuntimeException: B方法抛出了异常

上述方法执行的结果:B方法新增成功了,A新增失败了。B抛出了RuntimeException

3.6 MANDATORY

MANDATORY的含义是强制性的,义务的。字面意思也能够很明白的表达出该事务传播机制的特点。就是必须要有事务,没有就抛出异常。

这个传播行为就是不管咋地我都要在事务中,如果当前方法在事务中,就加入当前事务中,如果不在事务中,就抛出异常。

  1. 当前方法存在事务
    @Override
    @Transactional
    public void bookTransaction() {
        String transactionName = TransactionSynchronizationManager.getCurrentTransactionName();
        log.info("调用者中事务:{}", transactionName);
        bookMapper.insert(new Book().setAuthor("zpg"));
        //传播事务
        titleService.titleTransaction();
    }
    
    @Override
    @Transactional(propagation = Propagation.MANDATORY)
    public void titleTransaction() {
        String transactionName = TransactionSynchronizationManager.getCurrentTransactionName();
        log.info("被调用者中事务:{}", transactionName);
        titleMapper.insert(new Title().setName("第一章"));
    }
    
    // 输出,被调用者加入到当前事务中
    调用者中事务:com.example.demo.transaction.service.impl.BookServiceImpl.requiredTest
    被调用者中事务:com.example.demo.transaction.service.impl.BookServiceImpl.requiredTest
    
  2. 当前方法不存在事务
@Override
public void bookTransaction() {
    String transactionName = TransactionSynchronizationManager.getCurrentTransactionName();
    log.info("调用者中事务:{}", transactionName);
    bookMapper.insert(new Book().setAuthor("zpg"));
    //传播事务
    titleService.titleTransaction();
}
@Override
@Transactional(propagation = Propagation.MANDATORY)
public void titleTransaction() {
    String transactionName = TransactionSynchronizationManager.getCurrentTransactionName();
    log.info("被调用者中事务:{}", transactionName);
    titleMapper.insert(new Title().setName("第一章"));
}
// 输出,无事务则抛异常
调用者中事务:null
org.springframework.transaction.IllegalTransactionStateException: No existing transaction found for transaction marked with propagation 'mandatory'

3.7 NEVER

不可能有事务,有事务就抛出异常。
此传播方式就是不管怎么样,谁调用我,你就不许有事务,否则我就抛出异常。

  1. 调用者有事务
@Override
@Transactional
public void bookTransaction() {
    String transactionName = TransactionSynchronizationManager.getCurrentTransactionName();
    log.info("调用者中事务:{}", transactionName);
    bookMapper.insert(new Book().setAuthor("zpg"));
    //传播事务
    titleService.titleTransaction();
}
@Override
@Transactional(propagation = Propagation.NEVER)
public void titleTransaction() {
    String transactionName = TransactionSynchronizationManager.getCurrentTransactionName();
    log.info("被调用者中事务:{}", transactionName);
    titleMapper.insert(new Title().setName("第一章"));
}
// 输出,调用者有事务,则被调用者就抛异常
调用者中事务:com.example.demo.transaction.service.impl.BookServiceImpl.requiredTest
org.springframework.transaction.IllegalTransactionStateException: Existing transaction found for transaction marked with propagation 'never'

调用者和被调用者的方法执行均失败了。由于被调用者的事务传播机制是NEVER而且调用者存在事务,所以在调用者调用被调用者的时候,被调用者就抛出了异常,事务就会回滚。异常就抛给了调用者,调用者也存在事务,所以也失败了。

  1. 调用者无事务
@Override
public void bookTransaction() {
    String transactionName = TransactionSynchronizationManager.getCurrentTransactionName();
    log.info("调用者中事务:{}", transactionName);
    bookMapper.insert(new Book().setAuthor("zpg"));
    //传播事务
    titleService.titleTransaction();
}
@Override
@Transactional(propagation = Propagation.NEVER)
public void titleTransaction() {
    String transactionName = TransactionSynchronizationManager.getCurrentTransactionName();
    log.info("被调用者中事务:{}", transactionName);
    titleMapper.insert(new Title().setName("第一章"));
}
// 输出,调用者无事务,被调用者虽然有事务,但是会以无事务方式执行
调用者中事务:null
被调用者中事务:com.example.demo.transaction.service.impl.TitleServiceImpl.requiredTest

4. 总结

在网上看到一个图,总结的非常好,在这里也分享给大家。

接下来我们总结一下各种传播方式下,调用者和被调用者是怎么操作事务的。注:A方法是调用者,B方法是被调用者。对于A方法来说,就两种情况:有事务和无事务,而对于B方法来说,有七种情况,下面看看每种情况下B方法是怎样操作事务的。

在这里插入图片描述

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值