@Transactional注解

**

通过看大神的文章,写下我的心得体会,留给自己今后参考吧–@Transactional注解

**
文章通过引用商品售出,减库存,生成订单信息来引入了@Transactional这个注解
在这里插入图片描述
他说他有一个 func 方法,这个方法里面干了两件事:

1.先查询数据库里面的商品库存。
2.如果还有库存,那么对库存进行减一操作,模拟商品卖出。
对于第二件事,提问的同学其实写了两个操作在里面,所以我再细分一下:

2.1 对库存进行减一操作。
2.2 在订单表插入订单数据。
很显然,这两个操作都会对数据库进行操作,且应该是应该原子性的操作。

所以,在方法上加了一个 @Transactional 注解。
接着,为了解决并发问题,又将业务用lock包裹起来,非常完美!
那么问题来了,mysql数据库默认的的隔离机制是可重复读

事务的开始是在lock之后,那么事务的提交呢,是在unlock之前还是之后呢?

如果之前的话:
加锁–>查询库存–> 库存减一–>生成订单–>解锁
在这里插入图片描述
但是,如果事务的提交是在 unlock 之后,那么有意思的事情就出现了,你很有可能发生超卖的情况。直接上图
在这里插入图片描述
这个例子就是:
AB两个人来抢库存,
假如只剩1件商品,
A先进来,加锁,查询库存,1,减库存,事务还未提交,解锁,
此时,库存仍未1,
B抢到锁,查询库存,==1,
现在就导致了超卖的问题
在这里插入图片描述

事务开启

在这里插入图片描述
把数据库连接切换为手动提交。

那么事务的启动有哪些方式呢?

第一种:使用启动事务的语句,这种是显式的启动事务。比如 begin 或 start transaction 语句。与之配套的提交语句是 commit,回滚语句是 rollback。

第二种:autocommit 的值默认是 1,含义是事务的自动提交是开启的。如果我们执行 set autocommit=0,这个命令会将这个线程的自动提交关掉。意味着如果你只执行一个 select 语句,这个事务就启动了,而且并不会自动提交。这个事务持续存在直到你主动执行 commit 或 rollback 语句,或者断开连接。

很显然,在 Spring 里面采用的是第二种方式。

而上面的代码 con.setAutoCommit(false) 只是把这个链接的自动提交关掉。

事务真正启动的时机是什么时候呢?

前面说的 begin/start transaction 命令并不是一个事务的起点,在执行到它们之后的第一个操作 InnoDB 表的语句,事务才算是真正启动。

如果你想要马上启动一个事务,可以使用 start transaction with consistent snapshot 这个命令。需要注意的是这个命令在读已提交的隔离级别(RC)下是没意义的,和直接使用 start transaction 一个效果。

回到在前面的问题:什么时候才会执行第一个 SQL 语句?

就是在 lock 代码之后。

所以,显然事务的开启一定是在 lock 之后的。

这一个简单的“显然”,先给大家铺垫一下。

接下来,给大家上个动图看一眼,更加直观。

首先说一下这个 SQL:

select * from information_schema.innodb_trx;

不多解释,你只要知道这是查询当前数据库有哪些事务正在执行的语句就行。

最后,我们把目光转移到这个方法的注释上:
在这里插入图片描述
写这么长一段注释,意思就是给你说,这个参数我们默认是 ture,原因就是在某些 JDBC 的驱动中,切换为自动提交是一个很重的操作。

那么在哪设置的为 true 呢?

没看到代码,我一般是不死心的。

所以,一起去看一眼。
在这里插入图片描述
在这里插入图片描述
可以看到,默认确实是 true。

先给大家看一下我的代码
在这里插入图片描述
整个的流程是这样的:
1.先拿锁。
2.查询库存。
3.判断是否还有库存。
4.有库存则执行减库存,创建订单的逻辑。
5.没有库存则返回。
6.释放锁。

在这里插入图片描述
大神的案例是这个样子的:
在这里插入图片描述
在上面的示例代码的情况下,事务的提交在 unlock 之后。

原因在这里
在这里插入图片描述

前面我们聊事务开启的时候,说的是第 382 行代码。

然后 try 代码块里面执行的是我们的业务代码。

现在,我们要研究事务的提交了,所以主要看我框起来的地方。

首先 catch 代码块里面,392 行,看方法名称已经非常的见名知意了:

completeTransactionAfterThrowing 在抛出异常之后完成事务的提交。

你看我的代码,只是用到了 @Transactional 注解,并没有指定异常。

那么问题就来了:

Spring 管理的事务,默认回滚的异常是什么呢?

如果你不知道答案,就可以带着问题去看源码。

如果你知道答案,但是没有亲眼看到对应的代码,那么也可以去寻找源码。

如果你知道答案,也看过这部分源码,温故而知新。

先说答案:默认回滚的异常是 RuntimeException 或者 Error。

如果异常类型是 RuntimeException 或者 Error 的子类,那么就返回 true,即需要回滚,调用 rollback 方法:
如果返回为 false,则表示不需要回滚,调用 commit 方法:

这个代码块里面,try 我们也聊了,catch 我们也聊了。

就差个 finally 了。

我看网上有的文章说 finally 里面就是 commit 的地方。

错了啊,老弟。

这里只是把数据库连接给重置一下。

Spring 的事务是基于 ThreadLocal 来做的。在当前的这个事务里面,可能有一些隔离级别、回滚类型、超时时间等等的个性化配置。

不管是这个事务正常返回还是出现异常,只要它完事了,就得给把这些个性化的配置全部恢复到默认配置。

所以,放到了 finally 代码块里面去执行了。

真正的 commit 的地方是这行代码:
在这里插入图片描述
那么问题又来了:

走到这里来了,事务一定会提交吗?

话可别说的那么绝对,兄弟,看代码:

org.springframework.transaction.support.AbstractPlatformTransactionManager#commit

在这里插入图片描述

在 commit 之前还有两个判断,如果事务被标记为 rollback-only 了,还是得回滚。

而且,你看日志。

我这事务还没提交呢,锁就被释放了?
在这里插入图片描述

现在我们知道问题的原因了。解决方案其实都呼之欲出了嘛。正确的使用锁,把整个事务放在锁的工作范围之内:

在这里插入图片描述
上面的代码事务是失效的,正确的如下
在这里插入图片描述

大佬原文

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值