纯净、安全、绿色的下载网站

首页|软件分类|下载排行|最新软件|IT学院

当前位置:首页IT学院IT技术

Spring事务@Transactional 基于Spring中的事务@Transactional细节与易错点、幻读

日常打BUG   2021-11-18 我要评论
想了解基于Spring中的事务@Transactional细节与易错点、幻读的相关内容吗日常打BUG在本文为您仔细讲解Spring事务@Transactional的相关知识和一些Code实例欢迎阅读和指正我们先划重点:Spring事务,@Transactional事务下面大家一起来学习吧

ACID事务内的一组操作具有 原子性 、一致性、隔离性、持久性

  • Atomicity(原子性):一个事务(transaction)中的所有操作要么全部完成要么全部不完成不会结束在中间某个环节事务在执行过程中发生错误会被恢复(Rollback)到事务开始前的状态就像这个事务从来没有执行过一样
  • Consistency(一致性):在事务开始之前和事务结束以后数据库的完整性没有被破坏这表示写入的资料必须完全符合所有的预设规则这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作
  • Isolation(隔离性):数据库允许多个并发事务同时对其数据进行读写和修改的能力隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致事务隔离分为不同级别包括读未提交(Read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(Serializable)
  • Durability(持久性):事务处理结束后对数据的修改就是永久的即便系统故障也不会丢失

为什么要使用事务?

就是一组操作中存在着多个更新修改操作并且要满足事务的相关要求所以就需要使用到事务最常见的例子就是银行两个账户间的转账包含的A扣款 、B到账等多个操作这些个操作需要具备事务的特性比如说要么A成功扣款B也成功到账不能出现A扣款了B没到账(原子性)也不能出现现在AB都处理成功了后续又出现A账户的钱又增多了(持久性)也不能出现A账号初始余额充足两个并发处理导致出现余额为负的情况(隔离性)

如何使用事务?

在spring中可以使用声明性的注解事务即在有需要使用的方法、类上用@Transactional

修饰即可修饰的方法、类就是这个事务的包裹区域出现了对应的异常就会在AOP中触发回滚

默认的回滚是错误与运行异常不包括检验异常

rollbackFor参数支持用户自行设置例如可定义异常跟运行异常如下所示也支持自定义异常类

    @Transactional(rollbackFor = { Exception.class, RuntimeException.class })

默认的事务传播机制是Propagation.REQUIRED

事务的传播本质确定好事务的限制区域即哪些代码是受到事务保护的出现异常可以回滚

细节点:

  • 代码出现事务配置的异常在事务内的会自动回滚如果在对应的方法体内使用了try catch捕获异常异常没有抛出去那就不会回滚需要手动回滚了在catch语句中增加手动回滚的TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();语句
  • public 方法的事务才生效

事务的传播带来的几种结果

  • 外层没有事务的话内在的子方法没有的就没有有事务的就会有事务有事务的效果形同Propagation.REQUIRES_NEW互相独立每个都是一个不同的新事务
  • 外层有事务的话那这个整个方法都在一个事务的区域范围内内外任何一处回滚都是整个回滚但是Propagation.NESTED修饰的内部方法可以单独回滚掉自己这个内部方法作为一个嵌入子事务所具有的独特性
  • 外层有还是没有事务Propagation.REQUIRES_NEW修饰的方法都是作为一个独立的事务自己独立控制回滚与提交与外层事务无关联

此处举例的事务指的是 默认值为 Propagation.REQUIRED的传播行为以及Propagation.NESTED的传播行为

两个特例

  • 同一个类中有A、B两个方法A调用B方法A没事务B有事务B有异常时回滚失败
A没事务 A有事务
B没事务 没有事务的效果 不分析 事务生效B的异常可以让整个A回滚
B有事务 事务失效 事务生效同上 不分析

若是A/B在同一个类中A方法有事务B方法没有事务这个时候事务会生效原因是异常传导到了A方法中

A方法没事务B方法有事务A调用B方法若是A/B在同一个类中B方法事务失效A/B在不同的类B方法有事务效果

原因分析:这是动态代理导致的当要执行B方法的回滚时此时A调用的B方法不是动态代理的那个类无法进行回滚

  • A方法循环调用B方法A方法有事务B方法启用新事务B方法处理成功一条提交一条的数据B方法遇到异常有异常的那条回滚不影响之前处理成功提交的数据

从之前的推断来看Propagation.REQUIRES_NEW修饰的内部方法独立一个新事务跟外层没有关系其实是两个事务了外层事务回滚内存的也不会回滚内层回滚也不影响外层事务

但是实际结果还是有点不太一样若是A/B在不同类中可以达到这个效果同一个类的话就会回滚失败跟上面AB方法调用的结果类似

究其原因还是由于使用了动态代理来进行事务AOP的此时的B方法一旦触发回滚就是事务回滚异常了那么要想一个类中两个方法间调用达到部分提交的效果需要使用ApplicationContext 上下文对象获取当前类对象再进行调用

// 使用 ApplicationContext 上下文对象获取该对象;
@Autowired
private ApplicationContext applicationContext; 
CurrentClass classService = applicationContext.getBean(CurrentClass.class); 
//再用这个对象去调用同类的其他方法
classService.b();

总结: 事务的实现依赖于动态代理因此在同一个类中使用了类的其他方法时就需要额外注意了只有使用动态代理的对象去调用方法时才会有事务回滚的操作

事务传播属性propagation

propagation 代表事务的传播行为默认值为 Propagation.REQUIRED总共的属性信息如下:

  • Propagation.REQUIRED:如果当前存在事务则加入该事务如果当前不存在事务则创建一个新的事务(默认传播行为一定会有一个事务)

( 也就是说如果A方法和B方法都添加了注解在默认传播模式下A方法内部调用B方法会把两个方法的事务合并为一个事务 )

  • Propagation.SUPPORTS:如果当前存在事务则加入该事务如果当前不存在事务则以非事务的方式继续运行(以当前是否有事务为标准可以有事务也可以没有事务)
  • Propagation.MANDATORY:如果当前存在事务则加入该事务如果当前不存在事务则抛出异常(要求当前有事务就能运行没有就会异常)
  • Propagation.REQUIRES_NEW:重新创建一个新的事务如果当前存在事务暂停当前的事务

( 当类A中的 a 方法用默认Propagation.REQUIRED模式类B中的 b方法加上采用 Propagation.REQUIRES_NEW模式然后在 a 方法中调用 b方法操作数据库然而 a方法抛出异常后b方法并没有进行回滚因为Propagation.REQUIRES_NEW会暂停 a方法的事务 )

  • Propagation.NOT_SUPPORTED:以非事务的方式运行如果当前存在事务暂停当前的事务(以非事务的方式运行当前有不报错)
  • Propagation.NEVER:以非事务的方式运行如果当前存在事务则抛出异常
  • Propagation.NESTED :如果当前存在事务则嵌套事务内执行如果不存在事务和 Propagation.REQUIRED 效果一样

( 也就是说如果A方法和B方法都添加了注解在A默认传播模式下,B方法加上采用 Propagation.NESTED模式A方法内部调用B方法A回滚B也会回滚但B回滚A不会回滚

数据库隔离级别

事务的隔离级别依赖于数据库的隔离级别mysql的默认隔离级别是可重复读(repeatable read)对应的效果是在一个事务内重复读取一个表中的数据一直会是一样的并不会读取到那些个此事务范围内其他事务 未提交(脏读)、已提交(不可重复读)的修改记录

在对数据进行测试隔离级别时需要先对数据库进行一系列的设置包括关闭自动提交、查看当前的隔离级别相关命令如下所示:

//由于变量autocommit分会话系统变量与全局系统变量, Value的值为ON表示autocommit开启
OFF表示autocommit关闭
show session variables like 'autocommit';
show global variables like 'autocommit';
 
//关闭当前会话的自动提交
set session autocommit=0;
 
//开启一个事务
start transaction; 
begin;   
 
//回滚
rollback;    
//提交事务
commot; 
//查看当前的隔离级别   查看全局、当前会话的隔离级别
select @@tx_isolation;
SELECT @@global.tx_isolation, @@session.tx_isolation;
 
//设置当前会话的隔离级别为read uncommitted级别:
set session transaction isolation level read uncommitted;
 
//设置当前会话的隔离级别为read committed级别:
set session transaction isolation level read committed;
 
//设置当前会话的隔离级别为repeatable read级别:
set session transaction isolation level repeatable read;
 
//设置当前会话的隔离级别为serializable级别:
set session transaction isolation level serializable;
 
//展示连接id
select connection_id();
 
//数据库超时设置查询
show session variables like '%timeout';

事务的隔离级别总共分为:未提交读(read uncommitted)、已提交读(read committed)、可重复读(repeatable read)、串行化(serializable)

下面将对这四种一一展开说明:

1、未提交读(会有脏读的现象)

A事务已执行但未提交B事务查询到A事务的更新后数据A事务回滚那么之前读取到的A事务为提交的数据就是脏数据了最低的隔离级别很少会使用到

---脏读读取到了未提交的数据(新增、修改和删除) 除此之外还有会不可重复读、幻读的现象

事务1设置如上所示隔离级别为读未提交关闭了自动提交此时开始一个事务看到的有两条数据

再打开一个窗口关闭自动提交然后进行新增改的操作

最后的结果如上所示事务1读取到了另一事务未提交时的新增、修改跟删除的数据

2、已提交读

(会有不能重复读的现象因为每次读取都是读最新的那就可能前后两次会有差异了)

会读取这一段时间内其他事务对这些数据的变更操作A事务执行更新B事务查询A事务又执行更新B事务再次查询时B事务前后两次查询到的数据不一致例如事务B要更新状态因此先进行一次查询此时状态为1一系列操作后马上就要更新了此时再次查询第二次查询出来的状态变成了2

---不可重复读一般指的是删除、更新、新增还会有幻读的现象

不可重复读的结果就是事务1能够读取到另外事务的 新增、修改、删除操作与脏读的区别在于一个是提交后才能读取到一个是未提交的实时操作就能读取到

3、可重复读 (有可能覆盖掉其他事务的操作)

可重复读是mysql数据的默认隔离级别也是使用的较多的一种隔离级别下面重点对其分析分析

A事务无论执行多少次只要不提交在这事务内同一个SQL的查询值永远都不变可以理解成A事务内的所有查询都是 查询A事务开始时那一瞬间的数据快照

幻读: 由于互相隔离以及可重复读的特性另一个事务也同时在处理同一数据的话就会有一种空幻的现象好像少了点什么例如两个事务都带id去插入同一数据那么后插入的数据会加锁执行失败(另一事务未提交)或者主键冲突(另一事务已提交)而插入失败后再去查询又会发现并没有找到重复那条数据的就会有种读到了空白的感觉少读取到了内容 幻读不仅是插入更新、删除也会有这样的现象的

现实的一个例子就是离银行还款日期之前A去查看账单表获取到了此次的账单数据求得了总和根据账单综合就将账单还清了并且还再次查询显示已经还清了此时A将本次的查询还款操作提交到数据库在开开心心下班前突然心血来潮再次进行了查询账单操作突然多了几条消费记录了需要再次还款A就感觉 提交事务前的查询有点幻读了少了几条数据

事务1在另一个事务提交后再对同样的数据做修改 删除 新增操作

---幻读一般值的是新增就是明明查询不到这条数据去新增时会报错

其实更新、删除也会有的例如更新同一条用id+status去更新是后提交的会更新失败这一特性也可用来加锁即CAS来更新数据这样后操作的肯定就不会覆盖前面的数据了

已经被删除的数据此时去更新也不会生效了在这个事务内再次查询还是删除前的那个数据快照

如果更新数据时只用id存在并发修改的情况那么后提交的必定覆盖之前事务的更新操作比如本来数据的状态是1事务2将数据状态有1->2而事务1看的的状态还是1事务1直接使用id更新的话将数据的状态变成了3事务1以为是1->3 其实是由 2->3中间的状态2直接就被覆盖了因此高并发的更新需要慎重

幻读总结: 很多对幻读的解释是一个事务在查询的同时另一个事务插入了数据然后前一个事务再次查询就会发现多了几条数据这个现象是不存在的如果出现了那说明当前的隔离级别是读已提交了

可重复读中的就是说同一个事务了多次查询返回的数据肯定是一样的这是毋庸置疑的这也是与读已提交的区别因此只有在当前事务提交后再次查询才会刷新到另一事务的改变

那么我理解的幻读就是在另一事务新增数据并提交后此时的事务去新增同样一条数据会报错的而此时再去查询又是查无数据这种现象才是幻读

更新数据层面就是事务2已经将数据的状态改变提交了事务1用旧的状态作为条件去更新影响行数会是0这也是一种幻读 更新已经被删除的数据也是影响行数为0

数据库最终的执行还是串行的只是在前置的一些操作可以并发最终更新到数据库只能是有一条成功由于一些规则的设置就会出现上述的现象了

4、串行化(没有并发操作)

串行化是最高的隔离级别即事务排队串行执行了没有了并发操作也不会发生上述所说的脏读、不可重复读、幻读的现象这个的使用场景不多理解起来也较为的简单

总结: 数据库的隔离级别就是一个事务内对于另一事务的并发操作会有怎么样的效果

  • 另一事务操作时就能看到修改后的数据就是读未提交
  • 另一事务操作并提交后 能看到修改后的数据就是读已提交
  • 另一事务操作提交后当前事务依旧看不到相应的修改事务开始什么数据事务结束也是读取到同样的数据就是可重复读

所有的事务都排队依次执行了一次只能有一个进行修改没有了并行就是串行化

Spring事务隔离级别比数据库事务隔离级别多一个default

除了上述的四个隔离级别多出来 DEFAULT (默认)这是一个PlatfromTransactionManager默认的隔离级别即使用数据库默认的事务隔离级别另外四个与JDBC的隔离级别相对应可以显性去指定其隔离级别

以上为个人经验希望能给大家一个参考也希望大家多多支持


相关文章

猜您喜欢

  • C语言编程必背代码 C语言编程入门必背的代码实例整理大全

    想了解C语言编程入门必背的代码实例整理大全的相关内容吗程序媛张小妍在本文为您仔细讲解C语言编程必背代码的相关知识和一些Code实例欢迎阅读和指正我们先划重点:C语言编程入门,C语言必背代码下面大家一起来学习吧..
  • JPA Specification查询排序 JPA Specification常用查询+排序实例

    想了解JPA Specification常用查询+排序实例的相关内容吗afaye_在本文为您仔细讲解JPA Specification查询排序的相关知识和一些Code实例欢迎阅读和指正我们先划重点:JPA,Specification常用查询,JPA,Specification排序下面大家一起来学习吧..

网友评论

Copyright 2020 www.fresh-weather.com 【世纪下载站】 版权所有 软件发布

声明:所有软件和文章来自软件开发商或者作者 如有异议 请与本站联系 点此查看联系方式