【spring】spring 的事务(transaction) 四 嵌套事务PROPAGATION_NESTED


相关文章
spring 的事务(transaction) 一 基础概念介绍
spring 的事务(transaction) 二 陷阱
spring 的事务(transaction) 三 try catch对事务的影响
spring 的事务(transaction) 四 嵌套事务PROPAGATION_NESTED

概述

spring事务的原理是什么?

  • 首先mysql这样的数据库本身是支持事务的,有不同的事务隔离级别,事务分为手动开启事务和自动开启事务,参见 【mysql】MYSQL事务的开启与提交命令答疑,通过底层的支持,可以实现多条sql 原子化,要么都执行,要么都不执行
  • spring事务采用注解生成代理对象,把默认的自动开启事务变为手动开启,这样 多条sql语句都执行完后,才会提交事务

1. 什么是嵌套事务PROPAGATION_NESTED

我们回顾下spring 的事务(transaction) 一 基础概念介绍中的PROPAGATION_NESTED概念。

PROPAGATION_NESTED:
嵌套事务呈现父子事务概念,二者之间是有关联的,核心思想就是子事务不会独立提交,而是取决于父事务,当父事务提交,那么子事务才会随之提交;如果父事务回滚,那么子事务也回滚。

与此相反,PROPAGATION_REQUIRES_NEW的内层事务,会立即提交,与外层毫无关联。

但是子事务又有自己的特性,那 就是可以独立进行回滚,不会引发父事务整体的回滚(当然需要try catch子事务,避免异常传递至父层事务,如果没有,则也会引发父事务整体回滚)。这个特性比较有意思,虽然不能独立提交,但是可以独立回滚,因此,如果存在ABC 三个子事务,那么每个子事务都可以独立回滚,子事务类似一个游戏中的保存点,假设某个时间点,创建了一个保存点A,角色有10发子弹,主线继续发生时,对应执行某个子事务内的逻辑,如果游戏角色打了4发子弹,剩余6发子弹时挂了,点击返回上一个保存点,可以重新玩一次,此时该角色又是10发子弹,对应的就是子事务发生异常,子事务回滚到事务执行之前的那个点,放佛从来没有执行过该子事务一样,数据库的数据也不会发生变更。更重要的是,游戏角色仅会返回到某个进度的保存点,而不是返回到游戏的开始点,否则进度丢了,都想骂人了。

子事务可以独立回滚,也可以通过传递异常,让父事务也回滚,根源在于用户策略,在父事务通过try catch 对子事务进行包裹,灵活策略;

该传播机制的特点是可以保存状态保存点,当前事务回滚到某一个点,从而避免所有的嵌套事务都回滚,即各自回滚各自的,如果子事务没有把异常吃掉,基本还是会引起全部回滚的。

2. PROPAGATION_NESTED 与PROPAGATION_REQUIRES_NEW的区别

PROPAGATION_NESTED 和PROPAGATION_REQUIRES_NEW的区别:
在这里插入图片描述
PROPAGATION_REQUIRES_NEW内层事务执行完就立即提交。

PROPAGATION_NESTED 子事务执行完,不会立即提交,而是等待外层事务完成后一起提交。

2.1 验证PROPAGATION_REQUIRES_NEW内层事务执行完就立即提交

我们复用 spring 的事务(transaction) 三 try catch对事务的影响中的代码,在 “1. 非异常用例”章节中,会分别向2个表里面各自插入一条数据,那么我们通过断点方式,来查看独立事务的提交。

我们在UserServiceImpl中打断点,为了打断点,我们需要增加一行打印,作用是在内层事务执行后打断点:

 System.out.println("addOrder finished");

完整方法代码:

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    @Override
    public void addUser(int id, String name) {
        jdbcTemplate.execute("insert into `student` values (" + id + ",'" + name + "')");

        try {
            //注意:此时有try catch
            orderService.addOrder(3, "110");
            System.out.println("addOrder finished");
        } catch (Exception e) {
            System.out.println(e);
        }


    }

在这里插入图片描述

当代码执行到断点处时,我们看下数据库:
在这里插入图片描述
外层事务未完成提交,未新增了一条数据

内层事务完成提交,新增了一条数据,说明外层和内存是互不影响的

2.2 验证PROPAGATION_NESTED 内层事务执行完未立即提交

我们修改内层事务为PROPAGATION_NESTED 类型,并且也在同样的位置打断点,查看数据库数据是否发生变化。

    @Transactional(propagation = Propagation.NESTED)
    @Override
    public void addOrder(int id, String price) {
        //order是mysql关键词,必须用`号(tab键上方的那个键,波浪线键)包裹起来
        jdbcTemplate.execute("insert into `order` values ("+id+",'"+price+"')");
    }

在这里插入图片描述

继续走完父事务的代码,发现,2个数据表都多了一条数据。

从整个例子来看,嵌套子事务是整个事务的一部分,提交时,要随着整体才能提交,但是又可以局部回滚,因此这是PROPAGATION_REQUIRES_NEW等做不到的,PROPAGATION_REQUIRES_NEW会独立提交,不是一个整体的概念。

2.3 验证PROPAGATION_NESTED 内层事务回滚

我们模拟在子事务触发非check异常:

    @Transactional(propagation = Propagation.NESTED)
    @Override
    public void addOrder(int id, String price) {
        //order是mysql关键词,必须用`号(tab键上方的那个键,波浪线键)包裹起来
        jdbcTemplate.execute("insert into `order` values ("+id+",'"+price+"')");
        System.out.println( 1/0);
    }

一般在父层有灵活的策略:

        try {
            //注意:此时有try catch
            orderService.addOrder(3, "110");
            System.out.println("addOrder finished");
        } catch (Exception e) {
           // 这个地方,可以做很多事情,比如 ServiceC.methodC();  
        }

在捕获子事务后, 避免异常往上层传递后,这个地方,可以做很多事情,比如 执行ServiceC.methodC(); ,开启另一个子事务,等等。

如果就按照当前的实现的话,子事务会回滚,而父事务顺利提交。

执行结果:
在这里插入图片描述

Spring框架提供了两种事务管理方式:声明式事务和编程式事务。 编程式事务是通过编写代码来实现事务管理的方式。在Spring中,使用编程式事务需要借助于事务管理器(TransactionManager)和编程式事务模板(TransactionTemplate)来进行操作。 首先,你需要配置一个事务管理器,例如使用Spring提供的DataSourceTransactionManager来管理事务。然后,你可以在代码中使用TransactionTemplate来执行事务操作。 下面是一个简单的示例代码: ```java import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.support.TransactionCallback; import org.springframework.transaction.support.TransactionTemplate; public class TransactionalService { private TransactionTemplate transactionTemplate; public void setTransactionTemplate(TransactionTemplate transactionTemplate) { this.transactionTemplate = transactionTemplate; } public void performTransactionalOperation() { transactionTemplate.execute(new TransactionCallback<Void>() { public Void doInTransaction(TransactionStatus status) { try { // 执行数据库操作等业务逻辑 // 如果发生异常,可以通过调用 status.setRollbackOnly() 设置回滚 } catch (Exception e) { status.setRollbackOnly(); } return null; } }); } } ``` 在上面的示例中,通过调用transactionTemplate.execute方法来执行需要进行事务管理的业务逻辑。如果发生异常,通过调用status.setRollbackOnly()方法设置事务回滚。 编程式事务相对于声明式事务更加灵活,可以根据业务需要精确控制事务的边界和行为。但是编程式事务的代码相对较多,容易引入冗余代码,同时也影响代码的可读性和维护性。 因此,在实际开发中,一般推荐使用声明式事务来管理事务,而编程式事务更适用于一些特殊情况下的事务管理需求。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值