spring事务源码详解

上一篇文章《spring事务源码之构建事务代理对象》中,详细讲解了构建事务代理对象的源码,本文会接下去讲解如何通过这个代理对象实现事务的功能。

隔离级别使用默认的 ISOLATION_DEFAULT

传播行为以 PROPAGATION_REQUIRED(默认) 和 PROPAGATION_REQUIRES_NEW 为例。

PROPAGATION_REQUIRED:如果存在一个事务,则支持当前事务。如果没有事务则开启一个新的事务。

PROPAGATION_REQUIRES_NEW :总是开启一个新的事务。如果一个事务已经存在,则将这个存在的事务挂起。

重新贴上测试代码

@Transactional
@Override
public void pay(Payment payment) {
	Item item = itemService.getById(payment.getItemId());
	payment.setUnitPrice(item.getUnitPrice());
	payment.setPayment(item.getUnitPrice() * payment.getAmount());
	payment.setPayTime(new Date());
	// 创建支付记录
	paymentMapper.insert(payment);
	// 扣减库存
	itemService.deductStock(payment.getItemId(), payment.getAmount());
	System.out.println( 1 / 0 );
}

@Transactional
@Override
public void deductStock(Integer id, Integer amount) {
	Item item = getById(id);
	item.setStock(item.getStock() - amount);
	itemMapper.updateById(item);
}

// 测试类中
@Test
void pay() {
	Payment payment = new Payment();
	payment.setUserId(1);
	payment.setItemId(1);
	payment.setAmount(2);
	paymentService.pay(payment);
}

回到上一篇文章讲到的地方,debug pay 方法,然后 F7 进入方法,会进到代理对象对象中。我把之前打印日志的切面去掉了,所以这里拦截器中只有一个事务的拦截器:TransactionInterceptor

接下来看看它具体是如何工作的:

首先跟前面讲过的 aop 一样,进入 proceed 方法:

它内部调用了 TransactionInterceptor 的 invoke 方法

从方法名 invokeWithinTransaction 也可以看出,是以事务方式调用方法,它传了一个回调函数,这个先不管,后面再看。首先看看 invokeWithinTransaction 里做了什么。

在这个方法里面,首先是获取这个方法的事务的参数 TransactionAttribute,里面包含了两个重要的信息:事务的传播行为(propagation) 和 隔离级别(isolation)

我没有做额外的配置,所以现在都是用的默认值,PROPAGATION_REQUIREDISOLATION_DEFAULT

然后创建事务管理器 PlatformTransactionManager

继续往下看,接下来看着就一目了然了:

1、创建事务
2、调用拦截器链及目标方法
3、抛出异常时结束事务
4、finally中清除事务信息
5、方法返回时提交事务

接下来详细看看这几个步骤:

一、deductStock 方法传播行为是 PROPAGATION_REQUIRED 的场景

1、创建事务

首先是 pay 方法:

这个方法中调用链实在太长,我画了个图来简单说明。

主要是通过事务管理器来开启一个事务,在里面会获取数据源,然后从数据源中获取 Connection 连接,将 Connection 包装为 ConnectionHolder 对象,后面通过这个对象来使用连接。然后就是最最最重要的一步: setAutoCommit(false),关闭事务的自动提交!sql 默认是自动提交的,而要使用事务的话,sql 的提交必须由我们自己来控制,所以需要设置 autoCommit 为 false!

再之后就是创建一个 TransactionInfo 对象和 TransactionStatus 对象,用于保存事务信息和事务的状态信息,后面有用。然后将事务跟当前这个线程绑定。

创建完成后,事务对象中, newTransaction 为 true,表示是新的事务。

调用 deducStock 方法时,也会被拦截,然后创建事务,主要流程还是一样,不过 newTransaction 是 false,表明它不是新的事务了。这个参数后面会用到。

2、调用拦截器链及目标方法

如果还有其他拦截器的话,交给下面的拦截器处理,否则,执行目标方法。这里由于只有这一个拦截器,所以直接执行目标方法了。这个跟前面讲的 aop 就一样了。

在调用 pay 方法时,里面会调用 item 的 deductStock 方法,所以在 pay 方法中,会走一遍 deductStock 方法的整体流程(包括创建事务,执行目标方法,回滚/提交事务等),走完deductStock 方法的流程,才会接下去走 pay 方法截下来的流程,比如异常时回滚、清除事务、提交事务等操作。

3、抛出异常时结束事务

由于我写了一个错误的计算,所以会抛出异常,看看捕获到异常后,completeTransactionAfterThrowing 方法里做了些啥。

获取之前创建的事务管理器,进行回滚

由于当前这个是新的事务,所以会调用 doRollback

doRollback 内部还是取出 Connection 对象,调用它的 rollback() 方法进行回滚。

4、finally中清除事务信息

实际上就是把线程本地变量 transactionInfoHolder 给清空。

5、方法返回时提交事务

如果运行时出错了,抛出异常,那么是走不到这步的,因为在 completeTransactionAfterThrowing 方法中已经将异常抛给上一层处理了。

如果运行正常,会走到提交事务这一步。我将代码中的错误计算删除再看看效果。

首先是内部调用的 duductStock 方法:

会去获取事务管理器,然后调用它的 commit 方法尝试提交事务。

在执行提交的时候,会判断是否是新的事务,如果不是新的,那么不会提交事务!这里 deductStock 方法不是新的事务,它是加入进 pay 方法的事务的,所以这里不会提交。

然后是外层的 pay 方法:

依然是获取事务管理器,然后尝试 commit 

由于这个是最初创建的事务,是新的事务,所以这个会执行 doCommit 方法。 

内部还是获取 Connection 对象,然后调用它的 commit 方法,真正提交 sql 操作。事务提交完成。

到这边为止,一个方法的调用流程就结束了。

二、deductStock 方法传播行为是 PROPAGATION_REQUIRED_NEW 的场景

注解上修改 propagation 属性的值:

@Transactional(propagation = Propagation.REQUIRES_NEW)
@Override
public void deductStock(Integer id, Integer amount) {
    Item item = getById(id);
    item.setStock(item.getStock() - amount);
    itemMapper.updateById(item);
}

看异常场景(加上错误的代码:System.out.println( 1 / 0 );)最终现象我们知道,payment 的记录不会创建,因为会被回滚。但是 item 中的 stock 还是会被扣除,因为当前的传播行为下,调用 deductStock 方法时,会新开一个事务。所以前面的事务回滚,影响不到 deductStock 的提交。

那看看它是如何实现的:

首先调用 pay 方法,在 pay 方法真正调用之前,还是先创建一个事务对象,它的传播行为还是跟之前的一样 PROPAGATION_REQUIRED,因为我没改它的配置。我改的只是 deductStock 方法上事务传播行为。

然后调用真正的 pay 方法时,里面调用 deductStock 方法,此时针对 deductStock 方法的调用,还会创建一个事务对象,这个事务对象就跟之前不同了:

可以看到,它的传播行为是 PROPAGATION_REQUIRED_NEW,而且因为这个传播行为,它的 newTransaction 熟悉值是 true !

所以,在 deductStock 方法执行完毕后,commitTransactionAfterReturning 方法中,就能够调用到 doCommit 方法进行提交事务了!之前 PROPAGATION_REQUIRED 传播行为时,deductStock 方法是不能提交事务的。

deductStock 方法整个流程走完后,继续回到 pay 方法中,pay 方法出错然后回滚。但是这时候由于 deductStock 方法的事务已经提交,无法回滚了。pay 方法只能回滚其他未提交的操作。所以跟我们预想的现象符合。payment 的记录不会创建,因为被回滚了。但是 item 中的 stock 还是会被扣除,因为事务已经提交了。

无异常的情况下, pay 方法也能成功提交事务,不过两个方法的事务是区分开来的,虽然现象一样,但过程是不一样的。

到这里为止,就把两种场景讲完了。事务还有很多的场景,无法一一列举完,不过代码都在一起,所以看起来方便点,只是某些属性不一样,走的流程就不一样。不像上一篇讲的事务代理对象的获得,以及通知器的设置,代码跨度极大,而且需要了解 springboot 自动装配原理,spring ioc 的原理,看起来十分费劲。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值