数据库事务中的传播方式

  • PROPAGATION_REQUIRED:如果当前没有事务,就创建一个新事务,如果当前存在事务,就加入该事务,该设置是最常用的设置。

  • PROPAGATION_SUPPORTS:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行。‘

  • PROPAGATION_MANDATORY:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常。

  • PROPAGATION_REQUIRES_NEW:创建新事务,无论当前存不存在事务,都创建新事务。

  • PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。

  • PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。

  • PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。

这里数据库不好演示,我这里使用spring来演示一下

这里模拟一个创建订单的操作,创建订单时候,先扣减余额再扣减库存;

创建两张表

create table pay
(
    id         int auto_increment
        primary key,
    pay_amount int not null comment '付款金额'
);

create table inventory
(
    id  int auto_increment
        primary key,
    num int not null comment '库存数量'
);

创建一个springboot项目

public class OrderService {
    @Resource
    private PayService payService;
    @Resource
    private InventoryService inventoryService;
    //创建订单
    @Transactional(rollbackFor = Exception.class)
    public void createOrder(Integer num) {
        payService.payment(num);
        inventoryService.deduction(num);
    }
}
public class PayService {
    @Resource
    private PayMapper payMapper;
    //付款
    @Transactional(rollbackFor = Exception.class)
    public void payment(Integer num) {
        payMapper.insert(num);
    }
}
public class InventoryService {
    @Resource
    private InventoryMapper inventoryMapper;
    //扣减库存
    @Transactional(rollbackFor = Exception.class)
    public void deduction(Integer num) {
        inventoryMapper.insert(num);
        //模拟失败
        System.out.println(1 / 0);
    }
}
    @Resource
    private OrderService orderService;
    //调用创建订单
    @Test
    public void test1() {
        orderService.createOrder(1);
    }    

REQUIRED

  • 首先不模拟扣减库存失败(注释掉失败代码);运行测试类,发现数据库数据如下:

    id库存
    11
    id付款
    11

    付款表和金额表都成功插入数据;

  • 我们放开注释,模拟库存扣减失败,运行测试类:

    @Test
    public void test1() {
        orderService.createOrder(2);
    }

发现数据库数据如下:
付款表和金额表都未成功插入数据;

这里我们查看@Transactional发现里面的默认事务传播属性

Propagation propagation() default Propagation.REQUIRED;

创建订单的时候,付款和扣减库存都是使用的同一个事务,所以他们两个要么一起成功,要么一起失败。

REQUIRES_NEW

我们这里使用REQUIRES_NEW来尝试代码

    @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRES_NEW)
    public void payment(Integer num) {
        payMapper.payment(num);
    }

测试类

    @Test
    public void test1() {
        orderService.createOrder(3);
    }

这里仍然模拟下单失败的时候(放开注释),运行测试类,发现结果如下:

id付款
33

我们可以发现,我们付款了,但是我们没有扣减库存,因为我们使用的是REQUIRES_NEW事务传播i方式,它无论如何使用新的事务,所以它是一个单独的事务,它成功了,后面扣减库存的时候失败了,这两个独立的事务是不会相互影响的。

以上这两个事务传播方式是最常见的两种,也是除了NESTED 方式之外唯一会重新开启新事务的两种传播方式。

MANDATORY

这个传播方式从名称上就可以看出来,它必须存在与一个事务之中,否者会抛出异常。
我们去掉创建订单上面的开启事务方法

    //@Transactional(rollbackFor = Exception.class)
    public void createOrder(Integer num) {
        payService.payment(num);
        inventoryService.deduction(num);
    }
    //调用测试类
    @Test
    public void test1() {
        orderService.createOrder(4);
    }

可以发现程序直接报错,数据库没有任何数据

org.springframework.transaction.IllegalTransactionStateException: No existing transaction found for transaction marked with propagation 'mandatory'

NEVER

这个传播方式和MANDATORY刚好相反,由于篇幅所限这里就不给予演示了,下面贴出报错信息

org.springframework.transaction.IllegalTransactionStateException: Existing transaction found for transaction marked with propagation 'never'

SUPPORTS

这个传播方式很佛系,有则加入,没有就算了;修改代码如下:

//注释掉事务开启
//    @Transactional(rollbackFor = Exception.class)
    public void createOrder(Integer num) {
        payService.payment(num);
        inventoryService.deduction(num);
    }
//修改事务传播方式
    @Transactional(rollbackFor = Exception.class,propagation = Propagation.SUPPORTS)
    public void payment(Integer num) {
        payMapper.payment(num);
    }
//测试类
    @Test
    public void test1() {
        orderService.createOrder(5);
    }

查询数据库,可以发现:

id付款
55

付款了,但是没有库存,这里以为付款没有加入任何事务,所以没有回滚。

NOT_SUPPORTED

这种事务传播方式,永远不以事务的方式进行,跳过事务执行,这里不予以代码演示了。

NESTED

//开启事务
    @Transactional(rollbackFor = Exception.class)
    public void createOrder(Integer num) {
        payService.payment(num);
        inventoryService.deduction(num);
    }
//使用NESTED
    @Transactional(rollbackFor = Exception.class,propagation = Propagation.NESTED)
    public void payment(Integer num) {
        payMapper.payment(num);
    }
    //测试类
    @Test
    public void test1() {
        orderService.createOrder(6);
    }

这里我们发现数据库没有任何数据插入,这里和REQUIRED的结果是一样的,但是它其实是在当前事务中开启了一个子事务,我们接下来在付款中抛出异常并去掉扣减库存的异常,再次尝试一下,看结果如何:

    @Transactional(rollbackFor = Exception.class)
    public void createOrder(Integer num) {
    //这里捕获异常可能有点误解,这里前面的REQUIRED一样是可以捕获异常的,但是可以发现这里也会失败的,这里如果是使用这个trycatch是有一个坑在这里的,这个问题以后再来详解,如果需要了解,可以自行搜索:Transaction rolled back because it has been marked as rollback-only
    //稍微解释一下:这里因为我们开启的是一个新的事务,所以,我们可以手动捕获一样。
    //但是如果我们使用的是REQUIRED的话,创建订单和付款的时候是同一个事务,当事务发生异常的时候会设置为rollback-only,但是你继续操作,会报异常。
    //这里给出一篇文章:[Spring事务嵌套引发的血案---Transaction rolled back because it has been marked as rollback-only](https://blog.csdn.net/f641385712/article/details/80445912)
        try {
            payService.payment(num);
        }catch (Exception ignore){

        }
        inventoryService.deduction(num);
    }
    @Test
    public void test1() {
        orderService.createOrder(6);
    }

这里我们可以看到数据库中没有扣款记录,但是却扣减了库存;因为付款是嵌套事务,这个事务不会影响外部事务。(关于嵌套事务的知识请自行搜索)

题外话:
spring使用的AOP在同一个service中的调用是不可以实现的,所以我们如果同一个service进行调用,被调用的方法即使添加了@Transactional,也是不会起作用的,所以我们要么在不同的service中调用,要么我们使用另一种方法调用:

//这两个方法在同一个类中
//这样调用
((InventoryService) AopContext.currentProxy()).deduction2(num);
//这里事务会生效
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
    public void deduction2(Integer num) {
        inventoryMapper.insert(-num);
//        System.out.println(1/0);
    }
//这里需要引入aspects的maven配置。spring-aspects也可以
//        <dependency>
//            <groupId>org.springframework</groupId>
//            <artifactId>spring-aspects</artifactId>
//        </dependency>

到此为止,事务的传播属性介绍结束。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值