Bootstrap

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

目录

为什么要使用事务?

如何使用事务?

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

两个特例:

事务传播属性propagation

数据库隔离级别

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

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

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

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

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


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 方法的事务才生效

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

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

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

         

两个特例:

  • 同一个类中有A、B两个方法,A调用B方法。A没事务,B有事务,B有异常时,
;