SpringBoot - 事务介绍以及使用@Transactional 注解配置事务
SpringBoot中使用事务的具体实例
“事务”的相关介绍
1、事务的定义:一组具有原子性的逻辑工作单元
原子性:不可拆分,如转帐(银行有2000元,从A帐户上减100元,在B帐户加100元)
逻辑工作单元:工作单元具有一定的逻辑性,如转帐(先减后加)
2、ACID四个属性
-
Atomicity(原子性):一个事务(transaction)中的所有操作,或者全部完成,或者全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。即,事务不可分割、不可约简。
-
Consistency(一致性):在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设约束、触发器、级联回滚等。
-
Isolation(隔离性):数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括未提交读(Read
uncommitted)、提交读(read committed)、可重复读(repeatable
read)和串行化(Serializable)。 -
Durability(持久性):事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
3、事务的2种状态
已提交
已中止
原理:
checkpoint定时检查事务的状态 -> 已提交:写入磁盘 -> 已中止:回滚
4、锁策略:
行级锁
表级锁
共享锁
排它锁
……
如果我们主动使用,有可能会造成死锁,死锁一旦发生,就无法解决 --> 重启数据库 ,这个解决方法对于用户量大的系统是很致命的。
在SQL语句中不要使用加锁策略,非常容易死锁 -->(取决于) 并发数量
5、事务隔离级别:
-
read uncommitted
未提交读,一个事务可以读取另一个事务未提交事务的数据。
未提交读会引起脏读,不可重复读、幻读、丢失修改 -
read committed (orcale默认)
可提交读,一个事务可以读取到另一个事务提交的数据。
提交读解决了脏读,但还是会引起不可重复读、幻读、丢失修改。 -
repeatable read (mysql默认)
可重复读。一个事务对同一份数据读取到的相同,不在乎其他事务对数据的修改。
解决不可重复读。(实现机制是在事务A读取之后将这些数据添加行级锁,其他事务无法修改:待验证)会产生幻读,丢失修改。丢失修改可通过数据库互斥锁(悲观锁)来解决,
或者在字段中加入版本号(乐观锁),也可以代码中自行加锁。如果这个级别自动在查询后添加行级锁,那么其他修改无法执行,就能解决丢失修改问题。 -
serializable
可串行读,严格的数据隔离,要求事务序列化执行,事务只能一个接一个执行。(因为太严格,一般实际生产中不会用到)
缺点:严重影响并发能力。
优点:解决所有并发事务引起问题。
6.spring事务管理-隔离级别
跟上面的5.事务隔离级别 是对应的,不过这个表示的是Spring中的属性值
TransactionDefinition接口中定义了五个表示隔离级别的常量:
- TransactionDefinition.ISOLATION_DEFAULT: 使用后端数据库默认的隔离级别,Mysql 默认采用的 REPEATABLE_READ隔离级别 Oracle 默认采用的 READ_COMMITTED隔离级别.
- TransactionDefinition.ISOLATION_READ_UNCOMMITTED: 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读
- TransactionDefinition.ISOLATION_READ_COMMITTED: 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生
- TransactionDefinition.ISOLATION_REPEATABLE_READ: 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
- TransactionDefinition.ISOLATION_SERIALIZABLE: 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。
7、spring事务管理-事务传播机制
-
支持当前事务的情况:
TransactionDefinition.PROPAGATION_REQUIRED:
如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
TransactionDefinition.PROPAGATION_SUPPORTS:
如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
TransactionDefinition.PROPAGATION_MANDATORY:
如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。 -
不支持当前事务的情况:
TransactionDefinition.PROPAGATION_REQUIRES_NEW:
创建一个新的事务,如果当前存在事务,则把当前事务挂起。
TransactionDefinition.PROPAGATION_NOT_SUPPORTED:
以非事务方式运行,如果当前存在事务,则把当前事务挂起。
TransactionDefinition.PROPAGATION_NEVER:
以非事务方式运行,如果当前存在事务,则抛出异常。 -
其他情况:
TransactionDefinition.PROPAGATION_NESTED:
如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,等价于TransactionDefinition.PROPAGATION_REQUIRED。
这里需要指出的是,前面的六种事务传播行为是 Spring 从 EJB 中引入的,他们共享相同的概念。而PROPAGATION_NESTED 是 Spring 所特有的。以 PROPAGATION_NESTED 启动的事务内嵌于外部事务中(如果存在外部事务的话),此时,内嵌事务并不是一个独立的事务,它依赖于外部事务的存在,只有通过外部的事务提交,才能引起内部事务的提交,嵌套的子事务不能单独提交。如果熟悉 JDBC 中的保存点(SavePoint)的概念,那嵌套事务就很容易理解了,其实嵌套的子事务就是保存点的一个应用,一个事务中可以包括多个保存点,每一个嵌套子事务。另外,外部事务的回滚也会导致嵌套子事务的回滚。
SpringBoot中使用事务
1、使用注解@EnableTransactionManagement开启声明式事务支持
使用注解@Transactional配置事务(一般是某个业务逻辑层的方法)
具体的实现:
何时使用事务管理
- 所有select 操作,以及一条数据进行增删改操作 无需事务管理
- 批量增、批量删、批量改数据时,都需要使用事务管理。
- 主从表联动操作时,需要使用事务管理。
- 一个逻辑功能包含一系列操作作为整体运行时。
如银行开户操作(会对5张以上的表进行插入操作:用户信息表、账户信息表、用户操作信息表、身份验证信息表、储蓄卡信息表…)