Spring—事务及事务的传播机制
- 🔎事务的定义
- 🔎Spring—事务的实现
- 铺垫
- Spring 编程式事务
- Spring 声明式事务
- @Transactional 的参数
- 注意事项
- @Transactional 的工作原理
- 🔎Spring—事务的隔离级别
- MySQL—事务的隔离级别
- Spring—事务的隔离级别
- Spring—设置事务的隔离级别
- 🔎Spring—事务的传播机制
- Spring—事务传播机制的分类
- 对比加入事务与嵌套事务
- 总结加入事务与嵌套事务之间的区别
🔎事务的定义
将一组操作封装成一个执行单元, 即这一组操作一同成功 / 一同失败
举个栗子🌰
未使用事务
滑稽老哥给女神转账 520
由于某种原因, 女神并未收到转账的 520, 而滑稽老哥却被扣款 520
使用事务
滑稽老哥给女神转账 520
由于某种原因, 女神并未收到转账的 520
因为使用事务, 所以滑稽老哥的钱也被重新打回账户上
(一同成功 / 一同失败)
🔎Spring—事务的实现
Spring—事务的实现有 2 种方式
- 通过代码的方式手动实现事务(即 Spring 编程式事务)
- 通过注解的方式实现事务(即 Spring 声明式事务)
铺垫
后续内容针对数据表 userinfo 进行演示🍂
-- 创建用户表
drop table if exists userinfo;
create table userinfo(
id int primary key auto_increment,
username varchar(100) not null,
password varchar(32) not null,
photo varchar(500) default '',
createtime timestamp default current_timestamp,
updatetime timestamp default current_timestamp,
`state` int default 1
) default charset 'utf8mb4';
-- 添加一个用户信息
INSERT INTO `excnblog`.`userinfo` (`id`, `username`, `password`, `photo`, `createtime`, `updatetime`, `state`)
VALUES (1, 'admin', 'admin', '', '2021-12-06 17:10:48', '2021-12-06 17:10:48', 1);
Controller 层(UserController) → Service 层(UserService) → Mapper 层(UserMapper)🍂
UserController
代码在 Spring 编程式事务
UserService
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
public Integer add(UserInfo userInfo) {
return userMapper.add(userInfo);
}
}
UserMapper
@Mapper
public interface UserMapper {
int add(UserInfo userInfo);
}
UserMapper 对应的 SQL 语句
<insert id="add">
insert into userinfo(username, password) values(#{username}, #{password})
</insert>
UserInfo
@Data
public class UserInfo {
private Integer id;
private String username;
private String password;
private String photo;
private String createtime;
private String updatetime;
private Integer state;
}
Spring 编程式事务
Spring 编程式事务与 MySQL 操作事务类似, 分为 3 个部分
- 开启事务
- 提交事务
- 回滚事务
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@Autowired
private DataSourceTransactionManager transactionManager;
@Autowired
private TransactionDefinition transactionDefinition;
@RequestMapping("/add")
public Integer add(UserInfo userInfo) {
// 非空校验
if(userInfo == null || !StringUtils.hasLength(userInfo.getUsername())
|| !StringUtils.hasLength(userInfo.getPassword())) {
return 0;
}
// 1. 开启事务
TransactionStatus transactionStatus = transactionManager.getTransaction(transactionDefinition);
// 执行相关业务代码
int ret = userService.add(userInfo);
System.out.println("add : " + ret);
// 2. 提交事务
transactionManager.commit(transactionStatus);
// 3. 回滚事务
transactionManager.rollback(transactionStatus);
return ret;
}
}
验证效果(开启事务 + 提交事务)🍂
验证效果(开启事务 + 回滚事务)🍂
Spring 声明式事务
利用注解 @Transactional 实现 Spring 声明式事务
@Transactional 的特点
- 可以添加在类 / 方法上(默认情况下 @Transactional 针对的是 public 修饰的方法)
- 方法执行前自动开启事务, 方法执行期间出现异常 → 自动回滚事务, 方法执行完(无异常) → 自动提交事务
举个栗子🌰
方法执行期间出现异常 → 自动回滚事务
方法执行完(无异常) → 自动提交事务
@Transactional 的参数
参数 | 作用 |
---|---|
value | 配置多个事务管理器时, 可指定需要的事务管理器 |
transactionManager | 配置多个事务管理器时, 可指定需要的事务管理器 |
propagation | 事务的传播行为, 默认值为 Propagation.REQUIRED |
isolation | 事务的隔离级别, 默认值为 Isolation.DEFAULT |
timeout | 事务的超时时间, 默认值为 -1, 表示超时时间为无限大, 即事务将一直持续直到完成或手动回滚(如果超出设置的时间限制事务仍未完成, 则自动回滚事务) |
readOnly | 指定事务是否为只读, 默认值为 false(为了忽略不需要事务的方法, 例如读取数据, 设置 read-only 为 true) |
rollbackFor | 用于指定能够触发事务回滚的异常类型(可指定多个异常类型) |
rollbackForClassName | 用于指定能够触发事务回滚的异常类型(可指定多个异常类型) |
noRollbackFor | 抛出指定的异常类型, 不回滚事务(可指定多个异常类型) |
noRollbackForClassName | 抛出指定的异常类型, 不回滚事务(可指定多个异常类型) |
注意事项
方法内部存在异常, 但被捕获(try-catch)时, 仍会提交事务
即方法执行完(无异常) → 自动提交事务
代码示例如下
@RestController
@RequestMapping("/user")
public class UserController {
@RequestMapping("/insert")
@Transactional
public Integer insert(UserInfo userInfo) {
// 非空校验
if(userInfo == null || !StringUtils.hasLength(userInfo.getUsername())
|| !StringUtils.hasLength(userInfo.getPassword())) {
return 0;
}
int ret = userService.add(userInfo);
System.out.println("insert : " + ret);
try {
int num = 2 / 0;
} catch (Exception e) {
System.out.println(e.getMessage());
}
return ret;
}
}
针对上述情况, 想要回滚事务, 有 2 种解决方式
- 继续抛出异常
- 手动回滚事务(推荐)
继续抛出异常🍂
@RestController
@RequestMapping("/user")
public class UserController {
@RequestMapping("/insert")
@Transactional
public Integer insert(UserInfo userInfo) {
// 非空校验
if(userInfo == null || !StringUtils.hasLength(userInfo.getUsername())
|| !StringUtils.hasLength(userInfo.getPassword())) {
return 0;
}
int ret = userService.add(userInfo);
System.out.println("insert : " + ret);
try {
int num = 2 / 0;
} catch (Exception e) {
System.out.println(e.getMessage());
// 抛出异常
throw e;
}
return ret;
}
}
验证效果(抛出异常)
手动回滚事务🍂
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
@RestController
@RequestMapping("/user")
public class UserController {
@RequestMapping("/insert")
@Transactional
public Integer insert(UserInfo userInfo) {
// 非空校验
if(userInfo == null || !StringUtils.hasLength(userInfo.getUsername())
|| !StringUtils.hasLength(userInfo.getPassword())) {
return 0;
}
int ret = userService.add(userInfo);
System.out.println("insert : " + ret);
try {
int num = 2 / 0;
} catch (Exception e) {
System.out.println(e.getMessage());
// 手动回滚事务
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
return ret;
}
}
验证效果(手动回滚事务)
@Transactional 的工作原理
@Transactional 是基于 AOP 实现, AOP 使用动态代理实现
目标对象实现了接口, 默认采用 JDK 的动态代理 / 目标对象未实现接口, 默认采用 CGLIB 的动态代理
@Transactional 在开始执行业务之前, 通过代理开启事务, 执行成功之后提交事务(执行期间遇到异常回滚事务)
🔎Spring—事务的隔离级别
事务有 4 大特性(ACID)
- 原子性(Atomicity)
- 一致性(Consistency)
- 隔离性(Isolation)
- 持久性(Durability)
原子性🍂
定义
⼀个事务(Transaction)中的所有操作, 要么全部完成, 要么全部不完成, 不会结束在中间某个环节. 事务在执⾏过程中发⽣错误, 会被回滚(Rollback)到事务开始前的状态, 就像这个事务从来没有执⾏过⼀样
翻译
用户 A 给用户 B 转账
要么 A 成功转账给 B, B 收到转账的钱
要么 A 没能转账给 B, B 未收到转账的钱
不能出现 A 成功转账给 B, B 未收到钱等类似的情况
一致性🍂
定义
在事务开始之前和事务结束以后, 数据库的完整性没有被破坏. 这表示写⼊的资料必须完全符合所有的预设规则, 这包含资料的精确度, 串联性以及后续数据库可以⾃发性地完成预定的工作
翻译
用户 A 给用户 B 转账 520
A 账户被扣款 520, B 账户增加 520
不能出现 A 账户扣款 1000, B 账户增加 520 等类似的情况
隔离性🍂
定义
数据库允许多个并发事务同时对其数据进⾏读写和修改的能⼒, 隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不⼀致. 事务隔离分为不同级别, 包括读未提交(Read Uncommitted), 读已提交(Read Committed), 可重复读(Repeatable Read)和串行化(Serializable)
翻译(摘抄自网络)
将点餐过程理解为一个事务
制作 → 出餐 → 送餐 → 结算作为一个事务
不同的客户进行点餐是相互独立的, 并不会彼此影响 → 事物的隔离性
当一个事务影响其他事务时, 其他事务将会回滚
今日份的猪脚饭已经全部卖完, 再次点餐将会影响后续事务(制作 → 出餐 → 送餐 → 结算), 此时后续事务将会回滚
持久性🍂
定义
事务处理结束后, 对数据的修改就是永久的, 即便系统故障也不会丢失
翻译
用户 A 执行转账操作, 银行系统会开启事务, 将转账金额从 A 账户扣除, 将对应金额添加至 B 账户
当事务提交成功后, 系统会将更改持久化到数据库
即使系统发生故障, 数据库仍然能够恢复并保持转账操作的结果
MySQL—事务的隔离级别
MySQL—事务的隔离级别🍂
- READ UNCOMMITTED → 读未提交
- READ COMMITTED → 读已提交
- REPEATABLE READ → 可重复读(MySQL 默认的事务隔离级别)
- SERIALIZABLE → 串行化
事务隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
读未提交(READ UNCOMMITTED) | ✔ | ✔ | ✔ |
读已提交(READ COMMITTED) | ✘ | ✔ | ✔ |
可重复读(REPEATABLE READ) | ✘ | ✘ | ✔ |
串行化(SERIALIZABLE) | ✘ | ✘ | ✘ |
Spring—事务的隔离级别
Spring—事务的隔离级别🍂
- Isolation.DEFAULT → 以链接数据库的事务隔离级别为主
- Isolation.READ_UNCOMMITTED → 读未提交
- Isolation.READ_COMMITTED → 读已提交
- Isolation.REPEATABLE_READ → 可重复读
- Isolation.SERIALIZABLE → 串行化
Spring—设置事务的隔离级别
@Transactional(isolation = Isolation.DEFAULT)
public void setIsolationLevel() {
}
🔎Spring—事务的传播机制
事务的传播机制 → 事务的隔离级别 Plus 版
事务的隔离级别🍂
解决多个事务同时调用数据库的问题
事务的传播机制
解决一个事务在多个方法中传递的问题
Spring—事务传播机制的分类
Spring—事务传播机制的分类🍂
- Propagation.REQUIRED → 默认的事务传播级别. 表示如果当前存在事务, 则加入该事务 / 如果当前不存在事务, 则创建一个新的事务
- Propagation.SUPPORTS → 如果当前存在事务, 则加入该事务 / 如果当前不存在事务, 则以非事务方式运行
- Propagation.MANDATORY → 如果当前存在事务, 则加入该事务 / 如果当前不存在事务, 则抛出异常
- Propagation.REQUIRES_NEW → 表示创建一个新的事务. 如果当前存在事务, 则把当前事务挂起(强制必须有事务)
- Propagation.NOT_SUPPORTED → 以非事务方式运行, 如果当前存在事务, 则把当前事务挂起
- Propagation.NEVER → 以非事务方式运行, 如果当前存在事务, 则抛出异常
- Propagation.NESTED → 如果当前存在事务, 则创建一个事务作为当前事务的嵌套事务运行 / 如果当前不存在事务, 效果等同于 Propagation.REQUIRED
举个栗子🌰
对比加入事务与嵌套事务
UserController
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@Autowired
private LoginService loginService;
@RequestMapping("/insert")
@Transactional
public Integer insert(UserInfo userInfo) {
// 非空校验
if(userInfo == null || !StringUtils.hasLength(userInfo.getUsername())
|| !StringUtils.hasLength(userInfo.getPassword())) {
return 0;
}
int ret = userService.add(userInfo);
if(ret > 0) {
loginService.add();
}
return ret;
}
}
加入事务🍂
以@Transactional(propagation = Propagation.REQUIRED)
为例
UserService
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
@Transactional(propagation = Propagation.REQUIRED)
public Integer add(UserInfo userInfo) {
int ret = userMapper.add(userInfo);
System.out.println("添加 : " + ret);
return ret;
}
}
LoginService
@Service
public class LoginService {
@Transactional(propagation = Propagation.REQUIRED)
public Integer add() {
try {
int num = 1 / 0;
} catch (Exception e) {
System.out.println(e.getMessage());
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
return -1;
}
}
验证效果(执行期间出现异常 → 回滚全部事务)
嵌套事务🍂
以@Transactional(propagation = Propagation.
为例
UserService
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
@Transactional(propagation = Propagation.NESTED)
public Integer add(UserInfo userInfo) {
int ret = userMapper.add(userInfo);
System.out.println("添加 : " + ret);
return ret;
}
}
LoginService
@Service
public class LoginService {
@Transactional(propagation = Propagation.NESTED)
public Integer add() {
try {
int num = 1 / 0;
} catch (Exception e) {
System.out.println(e.getMessage());
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
return -1;
}
}
验证效果(执行期间出现异常 → 回滚部分事务)
总结加入事务与嵌套事务之间的区别
加⼊事务(REQUIRED) 和 嵌套事务(NESTED) 的区别
- 整个事务如果全部执行成功,结果均相同
- 如果事务执行期间失败了, 那么加入事务会将整个事务全部回滚;嵌套事务则会局部回滚,不会影响上⼀个方法中执行的结果
🌸🌸🌸完结撒花🌸🌸🌸