摘要:本文学习了Spring支持的事务管理。
环境
Windows 10 企业版 LTSC 21H2
MySQL 5.7.40
Java 1.8
Tomcat 8.5.50
Maven 3.6.3
Spring 5.2.25.RELEASE
1 定义
事务是一组逻辑处理单位,可以是执行一条SQL语句,也可以是执行几个SQL语句。
事务用来保证数据由一种存储情况变为另一种情况,组成事务的各个单元要么都执行成功,要么都执行失败。
2 事务特性
事务有四种特性:
- 原子性(Atomicity):事务是不可分割的最小执行单元,要么所有操作全部执行成功,要么全部失败回滚,不存在部分执行的中间状态。
- 一致性(Consistency):事务执行前后,数据库的所有完整性约束始终保持有效,数据从一个合法的一致状态平稳过渡到另一个合法的一致状态。
- 隔离性(Isolation):并发执行的事务之间互不干扰,每个事务的操作中间状态对其他事务不可见。
- 持久性(Durability):事务一旦提交成功,其对数据库做出的所有修改都会被永久保存到磁盘中,即使系统发生故障也不会丢失。
3 并发问题
对事务的操作分为两类:一种是读取事务(查),另一种是修改事务(增删改)。
单个事务的情况下,不会产生并发问题。如果多个事务在同一时刻操作同一数据可能会影响最终期望的结果,产生并发问题。
常见的并发问题:
- 脏写:更新时更新。事务一更新数据未提交,事务二更新相同数据并提交,事务一回滚,事务二提交的更新数据无效。
- 脏读:更新时读取。事务一更新数据未提交,事务二读取数据,事务一回滚,事务二读取的数据无效。
- 不可重复读:读取时更新或者删除。事务一读取数据,事务二修改数据或者删除数据,在事务二提交,事务一读取到不同的数据。
- 幻读:读取时插入。事务一读取数据,事务二插入数据,在事务二提交,事务一读取到插入的数据。
更新丢失:
- 第一类更新丢失:更新时更新。事务一更新数据并提交,事务二更新相同数据并回滚,事务一提交的更新数据无效。
- 第二类更新丢失:更新时更新。事务一更新数据并提交,事务二更新相同数据并提交,事务一提交的更新数据无效。
数据库不允许第一类更新丢失问题,第二类更新丢失问题无法通过隔离级别解决,一般使用锁解决。
4 隔离级别
为了解决不同程度的并发问题,SQL标准定义了隔离级别,每个级别都有各自的具体规则。
主要的隔离级别有四种:
- 读未提交(RU,Read Uncommitted):最低的隔离级别,允许读取未提交的数据变更,但是更新相同数据会被阻塞。可避免脏写。
- 读已提交(RC,Read Committed):大多数系统的默认隔离级别,允许读取已提交的数据变更。可避免脏写、脏读。
- 可重复读(RR,Repeatable Read):MySQL默认隔离级别,对同一字段的多次读取结果是一致的,除非数据被当前事务本身改变。可避免脏写、脏读、不可重复读。
- 串行化(S,Serializable):最高的隔离级别,通过强制事务排序解决并发问题。在每个读操作的数据行增加共享锁,可能导致大量超时和竞争。可避免脏写、脏读、不可重复读、幻读。
MySQL的默认隔离级别是RR级别,InnoDB存储引擎通过多版本并发控制(MVCC,Multiversion Concurrency Control)机制解决了幻读的问题。
5 传播行为
5.1 概念
当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。
事务的传播行为由传播属性决定,事务传播属性可以在@Transactional注解的propagation属性中定义。
5.2 分类
速记:
- REQUIRED:有则加入,无则新建
- REQUIRES_NEW:有也新建,无也新建
- SUPPORTS:有则加入,无则忽略
- NOT_SUPPORTED:有也忽略,无也忽略
- MANDATORY:有则加入,无则报错
- NEVER:有则报错,无则忽略
- NESTED:有则嵌套,无则新建
5.2.1 REQUIRED
默认传播行为。
有则加入,无则新建:
- 如果在其他事务方法里调用当前事务方法,当前方法就在其他方法的事务中执行。
- 如果没有事务方法调用,当前方法就开启一个新的事务执行。
5.2.2 REQUIRES_NEW
有也新建,无也新建:
- 如果在其他事务方法里调用当前事务方法,其他方法的事务将会被挂起,当前方法开启一个新的事务执行,当前方法执行完成后再继续执行其他方法的事务。
- 如果没有事务方法调用,当前方法就开启一个新的事务执行。
5.2.3 SUPPORTS
有则加入,无则忽略:
- 如果在其他事务方法里调用当前事务方法,当前方法就在其他方法的事务中执行。
- 如果没有事务方法调用,当前方法就不在事务中执行。
5.2.4 NOT_SUPPORTED
有也忽略,无也忽略:
- 如果在其他事务方法里调用当前事务方法,其他方法的事务将会被挂起,当前方法不在事务中执行,当前方法执行完成后再继续执行其他方法的事务。
- 如果没有事务方法调用,当前方法就不在事务中执行。
5.2.5 MANDATORY
有则加入,无则报错:
- 如果在其他事务方法里调用当前事务方法,当前方法就在其他方法的事务中执行。
- 如果没有事务方法调用,当前方法就抛出一个异常。
5.2.6 NEVER
有则报错,无则忽略:
- 如果在其他事务方法里调用当前事务方法,当前方法就抛出一个异常。
- 如果没有事务方法调用,当前方法就不在事务中执行。
5.2.7 NESTED
有则嵌套,无则新建:
- 如果在其他事务方法里调用当前事务方法,当前方法就在其他方法的事务的嵌套中执行。子事务回滚时不会导致主事务回滚,主事务提交和回滚时会导致子事务一起提交和回滚。
- 如果没有事务方法调用,当前方法就开启一个新的事务执行。
6 事务管理
6.1 编程式事务管理
在使用编程式事务管理时,必须在每个事务操作中包含额外的事务管理代码,通过将事务管理代码嵌入到业务方法中来控制事务的提交和回滚。
特点:
- 使用原生的事务管理是所有事务管理方式的基石,同时也是最典型的编程式事务管理。
- 相对于核心业务而言,事务管理代码显然属于非核心业务,会造成较大程度的代码冗余。
6.2 声明式事务管理
大多数情况下比编程式事务管理更好用,它将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理。
将事务管理方法作为横切关注点,通过AOP支持声明式事务管理。
特点:
- 业务代码中不包含任何事务管理代码。代码只关注核心业务逻辑,保持简洁和纯粹。
- 当事务需求改变时,通常只需修改配置或注解属性,而无需深入修改大量业务代码,降低了维护成本和风险。
条