一. 什么是事务
1.1 事务的概念
事务是一组操作,要么全部成功,要么全部失败回滚,不中间停、不部分生效。---事务的原子性
1.2 事务的操作
事务的操作主要有三步:
-
开启事start transaction/ begin (⼀组操作前开启事务)
-
提交事务: commit (这组操作全部成功, 提交事务)
-
回滚事务: rollback (这组操作中间任何⼀个操作出现异常,回滚事务)
注意:提交事务 与 回滚事务 只会操作其中一个
XML
-- 开启事务
start transaction;
-- 提交事务
commit;
-- 回滚事务
rollback;
二. Spring 中事务的实现
以前我们学习了MySQL中事务的实现,现在我们来学习Spring 中事务的实现
Spring 中的事务操作分为两类:
- 编程式事务(手动写代码操作事务).
- 声明式事务(利用注解自动开启和提交事务).
2.1 编程式事务(了解即可)
Spring 手动操作事务和上面 MySQL 操作事务类似, 有3个重要操作步骤:
• 开启事务(获取事务)
• 提交事务
• 回滚事务
java
@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
UserService userService;
@Autowired
DataSourceTransactionManager dataSourceTransactionManager;//JDBC 事务管理器
@Autowired
TransactionDefinition transactionDefinition;//定义事务属性
@RequestMapping("/registry")
public String registry(String name,String password){
// 开启事务
TransactionStatus transactionStatus = dataSourceTransactionManager
.getTransaction(transactionDefinition);
//⽤⼾注册
userService.registryUser(name,password);
//提交事务
dataSourceTransactionManager.commit(transactionStatus);
//回滚事务
//dataSourceTransactionManager.rollback(transactionStatus);
return "注册成功";
}
}
注意:
- DataSourceTransactionManager: 事务管理器.用来来获取事务(开启事务),提交或回滚事务
- TransactionDefinition 是事务的属性,在获取事务的时候需要将 TransactionDefinition 传递进去从而获得⼀个事务TransactionStatus
- 编程/手动式事务中,事务的回滚与提交都是我们程序员控制的,并且事务的回滚与提交是不能同时执行的----这就要我们自己加入条件去控制了
测试事务的提交:
java
@RequestMapping("/registry")
public String registry(String name,String password){
// 开启事务
TransactionStatus transactionStatus = dataSourceTransactionManager
.getTransaction(transactionDefinition);
//⽤⼾注册
userService.registryUser(name,password);
//提交事务
dataSourceTransactionManager.commit(transactionStatus);
return "注册成功";


事务提交成功
测试事务回滚:
java
@RequestMapping("/registry")
public String registry(String name,String password){
// 开启事务
TransactionStatus transactionStatus = dataSourceTransactionManager
.getTransaction(transactionDefinition);
//⽤⼾注册
userService.registryUser(name,password);
//回滚事务
dataSourceTransactionManager.rollback(transactionStatus);
return "注册成功";


通过查看表,我们发现我们注册的信息并未有出现----发生事务回滚,我们来看自动递增,更加能体现事务的回滚

我们现在就知道了,在事务回滚前,注册是成功了的(自动递增从2变为3),但是执行到事务回滚的代码后---数据发生回滚
注意:
- 事务回滚只能撤销对数据的修改(Insert/Update/Delete),无法撤销自增序列的分配。
- 事务回滚只是针对开启事务的代码块[自己管辖范围内 ]进行数据的回滚,未开启事务的代码依旧是正常执行的
- 事务回滚:撤销数据库里的操作(比如插入的用户数据、更新的余额);
- 事务回滚:不会修改 / 撤销 / 回滚任何内存中的变量、方法返回值、打印日志等。
2.2 声明式事务 @Transactional
2.2.1 @Transactional的使用
使用声明式事务,需要我们引入以下依赖:
XML
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
</dependency>
然后在需要事务的方法上添加@Transactional 注解就可以实现了.无需手动开启事务和提交事 务,进⼊方法时自动开启事务,方法执行完会自动提交事务,如果中途发生了没有处理的异常会自动回滚事务.
java
@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
UserService userService;
@RequestMapping("/registry")
@Transactional//开启事务
public String registry(String name,String password){
//⽤⼾注册
userService.registryUser(name,password);
return "注册成功";
}
}
程序运行,插入数据成功

修改程序,使之出现异常
java
@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
UserService userService;
@RequestMapping("/registry")
@Transactional//开启事务
public String registry(String name,String password){
//⽤⼾注册
userService.registryUser(name,password);
int a=10/0;//制造异常
return "注册成功";
}
}
调用该方法---事务回滚



当我们对异常进行捕获,再次运行程序:
java
@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
UserService userService;
@RequestMapping("/registry")
@Transactional//开启事务
public String registry(String name,String password){
//⽤⼾注册
userService.registryUser(name,password);
try {
int a=10/0;
}catch (Exception e) {
log.info("异常捕获成功");
}
return "注册成功";
}
}


事务提交成功

如果我们对异常进行捕获后再次抛出呢,再次运行程序:
java
@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
UserService userService;
@RequestMapping("/registry")
@Transactional//开启事务
public String registry(String name,String password){
//⽤⼾注册
userService.registryUser(name,password);
try {
int a=10/0;
}catch (Exception e) {
throw e;//捕获异常后抛出
}
return "注册成功";
}
}


程序抛出异常,事务回滚
从以上四个测试点说明:事务是否回滚只看整个方法是否抛出异常,只要抛出异常就会发生事务的回滚
从前面我们可以了解到,如果方法中抛出异常且只要我们进行捕获,事务就会提交成功,这违背了我们使用事务的初衷,所以有在捕获异常的前提下也能回滚事务的方法吗----当然有:捕获异常后手动回滚事务----看下面代码:
java
@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
UserService userService;
@RequestMapping("/registry")
@Transactional//开启事务
public String registry(String name,String password){
//⽤⼾注册
userService.registryUser(name,password);
try {
int a=10/0;
}catch (Exception e) {
//TransactionAspectSupport.currentTransactionStatus():获取到当前事务 setRollbackOnly():事务回滚
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
return "注册成功";
}
}
2.2.2 @Transactional 详解
@Transactional 源码如下:
java
package org.springframework.transaction.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.aot.hint.annotation.Reflective;
import org.springframework.core.annotation.AliasFor;
@Target({ElementType.TYPE, ElementType.METHOD})//既可以加在类上,也可以加在方法上
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Reflective
public @interface Transactional {
@AliasFor("transactionManager")
String value() default "";
@AliasFor("value")
String transactionManager() default "";
String[] label() default {};
Propagation propagation() default Propagation.REQUIRED;
Isolation isolation() default Isolation.DEFAULT;
int timeout() default -1;
String timeoutString() default "";
boolean readOnly() default false;
Class<? extends Throwable>[] rollbackFor() default {};
String[] rollbackForClassName() default {};
Class<? extends Throwable>[] noRollbackFor() default {};
String[] noRollbackForClassName() default {};
}
我们主要学习 @Transactional 注解当中的三个常见属性:
-
rollbackFor: 异常回滚属性.指定能够触发事务回滚的异常类型.可以指定多个异常类型
-
Isolation: 事务的隔离级别.默认值为 Isolation.DEFAULT
-
propagation: 事务的传播机制.默认值为Propagation.REQUIRED
rollbackFor:
@Transactional 默认只在遇到 运行时异常 和 Error 时才会回滚(图中圈出部分), 非运行时异常不回滚.

如果我们需要所有异常都回滚,需要来配置 @Transactional 注解当中的 rollbackFor 属性,通过 rollbackFor 这个属性指定出现何种异常类型时事务进行回滚:
java
@Transactional(rollbackFor = {Exception.class,Error.class})
总结:
- 在Spring的事务管理中,默认只在遇到运行时异常RuntimeException和Error时才会回滚.
- 如果需要回滚指定类型的异常,可以通过rollbackFor属性来指定.
2.3 事务隔离级别
记住:事务隔离级别存在的前提 = 多个事务(用户)同时操作同一张表 / 同一行数据
2.3.1 MySQL事务隔离级别
Mysql中也有事务的隔离级别,在这里我们不过多介绍,简单的回顾以下即可:
隔离级别 英文名称 脏读 不可重复读 幻读 核心说明 适用场景 读未提交 READ UNCOMMITTED 允许 允许 允许 最低级别,事务能读到其他事务未提交的数据 几乎不用,数据一致性极差 读已提交 READ COMMITTED 禁止 允许 允许 事务只能读到其他事务已提交的数据 多数数据库(Oracle、PostgreSQL)默认级别 可重复读 REPEATABLE READ 禁止 禁止 禁止 同一个事务内,多次读取同一数据结果一致 MySQL 默认级别,兼顾一致性与性能 串行化 SERIALIZABLE 禁止 禁止 禁止 最高级别,事务串行执行,完全无并发 对数据一致性要求极高的场景(如金融对账)
- 脏读 :读到其他事务未提交的脏数据(未提交 = 还没最终确定,后面可能提交,也可能回滚撤销)
- 不可重复读:同一事务内,两次读取同一数据结果不一致(在你这个事务还没结束的时候,别的事务已经把数据改了,并且提交成功了。)(同一事务 = 一次 begin 到一次 commit 之间的所有操作。)
- 幻读 :同一事务内,两次查询的数据行数不一致(在你的事务还没有结束时,其他事务对数据执行了插入 / 删除操作,并且已经提交成功,导致你再次查询时,结果集的行数发生变化)
2.3.2 Spring 事务隔离级别
Spring 事务隔离级别有五种:
| Spring 枚举值 | 中文名称 | 对应 SQL 标准 | 说明 |
|---|---|---|---|
DEFAULT |
数据库默认 | --- | 以当前连接数据库的默认事务隔离级别为准 |
READ_UNCOMMITTED |
读未提交 | READ UNCOMMITTED |
允许读取其他事务未提交的数据 |
READ_COMMITTED |
读已提交 | READ COMMITTED |
只能读取其他事务已提交的数据 |
REPEATABLE_READ |
可重复读 | REPEATABLE READ |
同一事务内多次读取同一数据结果一致 |
SERIALIZABLE |
串行化 | SERIALIZABLE |
事务串行执行,最高隔离级别 |
注意:
-
Spring 不自己实现隔离,只是告诉 MySQL 要用什么级别
-
最终执行隔离的是 MySQL 数据库
-
Spring 多一个
DEFAULT,MySQL 没有DEFAULT这种选项 -
Spring 用**
DEFAULT** 来自动适配数据库默认值,非常方便 -
只要级别名称对应,脏读、不可重复读、幻读 的表现完全相同,实际效果完全一样
三. Spring事务传播机制
3.1 什么是Spring 事务传播机制
当一个带有事务的方法,调用另一个也带有事务的方法时,事务该怎么传递、怎么合并、怎么新建,这就是事务传播行为。
比如有两个方法A,B都被 @Transactional 修饰, A方法调用B方法 A方法运行时,会开启⼀个事务.当A调用B时,B方法本身也有事务,此时B方法运行时,是加入A的事务,还是创建⼀个新的事务呢? 这个就涉及到了事务的传播机制.
3.2 事务的传播机制有哪些
| 传播行为枚举值 | 中文说明 | 核心规则 |
|---|---|---|
| Propagation.REQUIRED | 默认级别 | 当前有事务则加入;无事务则新建一个事务 |
| Propagation.SUPPORTS | 支持事务 | 当前有事务则加入;无事务则以非事务方式运行 |
| Propagation.MANDATORY | 强制事务 | 当前有事务则加入;无事务则直接抛出异常 |
| Propagation.REQUIRES_NEW | 新建独立事务 | 始终创建新事务;当前有事务则挂起,内外事务独立互不干扰 |
| Propagation.NOT_SUPPORTED | 不支持事务 | 始终以非事务运行;当前有事务则将其挂起 |
| Propagation.NEVER | 禁止事务 | 始终以非事务运行;当前有事务则抛出异常 |
| Propagation.NESTED | 嵌套事务 | 当前有事务则作为嵌套子事务运行;无事务则等价于 REQUIRED |
3.3 事务的传播机制详细讲解
A开启(不开启)事务,B开启事务,且在funA中调用了funB,那么funB是使用funA的事务,还是使用自己的事务----这些均取决与funB设置的事务的传播机制决定

- Propagation.REQUIRED(默认):如果A有事务,则B和A共用同一个事务(不使用B的事务);A没有事务,则B使用自己的事务,此时B事务的回滚/提交不会影响到A
- Propagation.SUPPORTS:如果A有事务,则B和A共用同一个事务(不使用B的事务);A没有事务,则B以非事务的方式运行(B自己的事务不起任何作用)
- Propagation.MANDATORY:如果A有事务,则B和A共用同一个事务(不使用B的事务);A没有事务,则B直接抛出异常(该机制要求A必须要有事务)
- Propagation.REQUIRES_NEW:不管 A 有没有事务,B都是只是使用自己事务;如果 A 有事务,就先把 A 的事务挂起,等 B 执行完提交后,再恢复 A 的事务。A 和 B 事务互不干扰、互不影响、各自回滚。
- Propagation.NOT_SUPPORTED:B始终以非事务方式运行,若A存在事务则将其挂起,B绝不参与任何事务。(B的报错不会引起A的事务回滚,A的事务回滚也不会影响到B操作数据的变化)
- Propagation.NEVER:B始终以非事务方式运行,若A存在事务,B直接抛出异常
- Propagation.NESTED:如果A有事务,则B使用自己的事务(作为嵌套事务)( B 报错回滚:只回滚 B 自己,不影响 A;A 报错回滚:连带着 B 一起回滚);若A不存在事务,则B使用自己的事务