一周时间深扒事务 总结代码演示篇 拿捏事务
事务
- 前言
- 文章内容简介
- 事务(理论) ⭐️⭐️⭐️⭐️
- 什么是事务⭐️⭐️⭐️⭐️
- 事务是怎么实现的(理论) ⭐️⭐️⭐️⭐️⭐️
- 事务和AOP的关系
- 代码解读 ⭐️⭐️⭐️⭐️⭐️
- 简单使用事务
- 开始测试
- 1. 执行出现异常是否回滚
- 🤔: 执行结果
- 2. 下面情况事务会不会生效
- 🤨:看一下控制台
- 🤔: 查看数据库
- 3. 下面事物会不会生效
- 数据库
- 阶段总结
- 详细解析
- Case 1: 类内部访问
- 1. 调用方不进行事物控制 被调用方进行事物控制
- 控制台
- 数据库
- 源码解析
- 解释图
- 源码:
- 详细解读
- 小总结
- Case2: Private 事务失效
- 简单解读:
- 小总结
- Case3 : 异常不匹配
- 第一种情况
- 第二种情况
- 小总结
- Case 4: 多线程事务失效
- 代码演示
- 小总结
前言
首先 作为一个开发 我们首先就是要保证数据的安全性和准确性 减少数据偏差 因为一点的数据偏差也可能造成 很大的事故 所以了解事务的正确使用是必备的 我们可能都知道什么是事务 事务的理论 ACID
随时都可以说出来 (如果不能立马在脑子里背出来就更要学习了) 其实在日常使用中 我们最重要的就是看事务是否生效 而不是加上事务就算完事了 循循渐进的去了解
文章内容简介
事务(理论) ⭐️⭐️⭐️⭐️
比较了解的可以直接跳过这一趴
什么是事务⭐️⭐️⭐️⭐️
事务具有四个特性:
原子性(Atomicity)
一致性(Consistency)
隔离性(Isolation)
持续性(Durability)
这四个特性也简称ACID。
原子性:
一个事务(transaction)中的所有操作,要么全部完成
,要么全部不完成
,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态
,就像这个事务从来没有执行过一样。
就像你买东西要么交钱收货一起都执行,要么要是发不出货,就退钱。(最小了,不可再分了)
一致性:
事务的一致性指的是在一个事务执行之前和执行之后数据库都必须处于一致性状态
。如果事务成功地完成,那么系统中所有变化将正确地应用,系统处于有效状态。如果在事务中出现错误,那么系统中的所有变化将自动地回滚,系统返回到原始状态,这个是在原子性的基础上
隔离性:
指的是在并发环境中,相互隔离 ,互不干扰
,当不同的事务同时操纵相同的数据时,每个事务都有各自的完整数据空间
。由并发事务所做的修改必须与任何其他并发事务所做的修改隔离。事务查看数据更新时,数据所处的状态要么是另一事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看到中间状态的数据。(只能吃自己碗里的 不能吃别人碗里的 各吃各的)
持续性:
指的是只要事务成功结束,它对数据库所做的更新就必须永久保存下来
。即使发生系统崩溃,重新启动数据库系统后,数据库还能恢复到事务成功结束时的状态。
具体定义
事务提供一种机制将一个活动涉及的所有操作纳入到一个不可分割的执行单元,组成事务的所有操作只有在所有操作均能正常执行的情况下方能提交,只要其中任一操作执行失败,都将导致整个事务的回滚。简单地说,事务提供一种“要么什么都不做,要么做全套(All or Nothing)”机制
。
事务是怎么实现的(理论) ⭐️⭐️⭐️⭐️⭐️
而事务的ACID是通过
InnoDB日志和锁
来保证。事务的隔离性
是通过数据库锁
的机制实现的,持久性
通过redo log(重做日志)
来实现,原子性和一致性
通过Undo log
来实现。UndoLog的原理很简单,为了满足事务的原子性,在操作任何数据之前,首先将数据备份到一个地方(这个存储数据备份的地方称为UndoLog)。然后进行数据的修改。如果出现了错误或者用户执行了ROLLBACK语句,系统可以利用Undo Log中的备份将数据恢复到事务开始之前的状态。
和Undo Log相反,RedoLog记录的是新数据的备份。在事务提交前,只要将RedoLog持久化即可,不需要将数据持久化。当系统崩溃时,虽然数据没有持久化,但是RedoLog已经持久化。系统可以根据RedoLog的内容,将所有数据恢复到最新的状态。
事务和AOP的关系
我们都知道AOP 的一个功能 就是事务所以AOP 和 Transaction 之间的关系是很密切的 说白了就是 事务就是由AOP 动态代理去处理的 也影响着事务是否生效 下面通过源码可以看出两者之间的关系
代码解读 ⭐️⭐️⭐️⭐️⭐️
简单使用事务
前期准备工作
- 数据库表结构
开始测试
1. 执行出现异常是否回滚
加上事务 @Transactional
注解 开启这个方法的事务监控 name 我们数据设定的是不能为空 我们进行置空 这个时候肯定回出现错误
这个时候第一个插入不会出错 第二个插入出错这个时候事务会生效吗 ?
🤔: 执行结果
如果这个时候不生效 的话 这个事务注解也没什么作用了 所以 事务是生效的 数据库无数据
2. 下面情况事务会不会生效
我们在Insert 之后手动抛出错误 这个时候 那么事务会不会回滚呢? 心中想一下答案
@Override
@Transactional
public void affairsExamination(Affairs affairs) throws Exception {
affairsMapper.insert(affairs);
throw new Exception("出现错误");
}
}
🤨:看一下控制台
错误准时出现 sql 语句也执行了 那么数据有数据吗?
🤔: 查看数据库
这个时候数据出现数据 为什么出现错误事务没有回滚呢
只要原因是 @Transaction
默认只会在遇到RuntimeException的时候才会回滚 也就是运行时异常才会回滚 非运行时异常不会回滚 上述异常 为非运行时异常
所以事务不会进行回滚处理
虽然 SQLException
是非运行时异常 但是因为在同一个事物下面所以事务还是会回滚
3. 下面事物会不会生效
同样是自自定义抛出错误 加上红框框里的内容事物会不会回滚
、
数据库 数据为空 数据回滚 rollbackFor = Exception.class
这个就是无论 运行时异常还是 非运行时异常 只要是Exception
下的都进行回滚。可以在使用事物的时候加上
数据库
阶段总结
-
@Transactional
默认只能回滚运行时异常 -
@Transactional(rollbackFor = Exception.class)
可以回滚 Exception下面的子类异常
详细解析
Case 1: 类内部访问
1. 调用方不进行事物控制 被调用方进行事物控制
这个时候我们抛出自定义 事务是否会回滚
控制台
数据库
如期报错 但是 数据添加成功 事物没有生效
源码解析
解释图
因为 @Transactional 的工作机制是基于 AOP
实现,AOP 是使用动态代理实现的,如果通过代理直接调用 insertData(),通过 AOP 会前后进行增强,增强的逻辑其实就是在 insertData() 的前后分别加上开启、提交事务的逻辑。
源码:
个人感觉这里是分割点 决定事务是否生效 下面的几个 失效场景也会用到
比较明显的两个注解 Modifier.isPublic(method.getModifiers()) && method.getDeclaringClass() != Object.class 这就对应了下面的这句话 仅对不是从java.lang.Object 派生的公共代理使用
详细解读
篇幅过长 请跳转另一个博客查看 :链接: 深扒事务内部类调用失效
小总结
- 事务失效的原因主要是因为没有获取到拦截器链 没有通知的资格 也就是 AOP 不会对方法做什么增强 仅仅是单纯的方法调用
反过来:
🌟🌟🌟🌟🌟
调用方添加@Transaction
这个时候 事务会生效 因为时通过代理持有者直接调用 affairExamination
前后增加开启和提交事务的逻辑 所以事务就生效了
数据库无数据。出现异常事务回滚
Case2: Private 事务失效
首先这个会出现编译提示
翻译: 使用“@Transactional”注释的方法必须是可覆盖的
出现这种提示 你还接着用 那就挺离谱的 该挨批 private 这种几乎没有使用场景。直接探究一下为什么 @Transactional
在private修饰下会失效的问题🤯
简单解读:
我们简单看一下代码 上面如果你认真看了 就知道事务是否生效是和AOP 动态代理有关联的 当没有进行AOP 动态代理的增强事务就不会生效了 如图一所示 AOP 只会代理 公共方法
图一:
会进行判断当前的修饰符是不是Public修饰的 。
这里会进行判断 是 public 修饰的就返回true。不是就返回false
小总结
这个其实就是AOP的特性 :只需要记住一点 在用Aop切入方法时,注意方法的类型要设置成public共有类型!
AOP 没办法切入 事务就是无效的
Case3 : 异常不匹配
这种有两种情况
第一种情况
看完上面应该知道 事务默认只能对运行时异常进行回滚 所以在使用 @Transactional 时没有增加 rollbackFor = Exception.class 导致出现异常 无法事务没有进行监听
点进rollbackOn 方法 发现 return ex instanceof RuntimeException || ex instanceof Error
这个都懂哈
返回false 就不执行回滚 直接就commit
第二种情况
异常捕获了。事务感知不到 所以在使用Try - catch 的时候注意
小总结
- 没有添加
rollbackFor = Exception.class
所以只能捕获运行时异常 - 不合理的Tty-catch 导致事务无法感知异常
Case 4: 多线程事务失效
代码演示
能走到这一步说明 子线程出现错误 对父线程没有影响 所以事务没有生效 有点想异常的第二种情况
小总结
由于子线程的异常不会被外部的线程捕获,所以父线程不抛异常,事务回滚没有生效。