前言🍭
❤️❤️❤️SSM专栏更新中,各位大佬觉得写得不错,支持一下,感谢了!❤️❤️❤️
在Spring框架中,事务管理是一种用于维护数据库操作的一致性和完整性的机制。Spring事务管理提供了灵活的方式来处理事务,包括事务的创建、提交、回滚以及事务的传播行为。
一、为什么需要事务?🍭
事务定义:
将一组操作封装成一个执行单元(封装到⼀起),要么全部成功,要么全部失败。
为什么要用事务?
比如转账分为两个操作:
第一步操作:A 账户 -100 元。
第二步操作:B 账户 +100 元。
如果没有事务,第一步执行成功了,第二步执行失败了,那么 A 账户平白无故的 100 元就“人间蒸 发”了。而如果使用事务就可以解决这个问题,让这⼀组操作要么⼀起成功,要么⼀起失败。
二、Spring 中事务的实现🍭
Spring 中的事务操作分为两类:
- 编程式事务(手动写代码操作事务)。
- 声明式事务(利用注解自动开启和提交事务)。
在开始讲解它们之前,咱们先来回顾事务在 MySQL 中是如何使用的。
1、MySQL 中的事务使用🍉
事务在 MySQL 有 3 个重要的操作:开启事务、提交事务、回滚事务,它们对应的操作命令如下:
-- 开启事务
start transaction;
-- 业务执行
-- 提交事务
commit;
回滚事务
rollback;
2、Spring 编程式事务(了解)🍉
Spring 手动操作事务和上面MySQL 操作事务类似,它也是有 3 个重要操作步骤:
- 开启事务(获取事务)。
- 提交事务。
- 回滚事务。
SpringBoot 内置了两个对象,DataSourceTransactionManager 用来获取事务(开启事务)、提交或回滚事务的,而TransactionDefinition 是事务的属性,在获取事务的时候需要将 TransactionDefinition 传递进去从而获得⼀个事务 TransactionStatus,实现代码如下:
package com.example.demo.controller;
import com.example.demo.entity.UserInfo;
import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.time.LocalDateTime;
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@Autowired
private DataSourceTransactionManager transactionManager;
@Autowired
private TransactionDefinition transactionDefinition;
@RequestMapping("/add")
public int add(UserInfo userInfo){
// 非空效验
if (userInfo == null || !StringUtils.hasLength(userInfo.getUsername())
|| !StringUtils.hasLength(userInfo.getPassword())) {
return 0;
}
// 1.开始事务
TransactionStatus transactionStatus =
transactionManager.getTransaction(transactionDefinition);
// 手动设置创建时间和修改时间的默认值
userInfo.setCreatetime(LocalDateTime.now().toString());
userInfo.setUpdatetime(LocalDateTime.now().toString());
int result = userService.add(userInfo);
System.out.println("添加:" + result);
// 2.回滚事务
transactionManager.rollback(transactionStatus);
return result;
}
}
因为回滚了事务,所以数据库中不会有wangwu这个用户。
从上述代码可以看出,以上代码虽然可以实现事务,但操作也很繁琐,有没有更简单的实现方法呢?请看下面声明式事务。
3、Spring 声明式事务(自动)🍉
声明式事务的实现很简单,只需要在需要的方法上添加 @Transactional 注解就可以实现了,无需手动开启事务和提交事务,进入方法时自动开启事务,方法执行完会自动提交事务,如果中途发生了没有处理的异常会自动回滚事务,具体实现代码如下:
@Transactional// 声明式事务(自动提交)
@RequestMapping("/insert")
public Integer insert(UserInfo userInfo) {
// 非空效验
if (userInfo == null || !StringUtils.hasLength(userInfo.getUsername()) ||
!StringUtils.hasLength(userInfo.getPassword())) {
return 0;
}
// 添加用户
int result = userService.add(userInfo);
return result;
}
接下里使用以下代码,分别设置 @Transactional 注解和不设置 @Transactional,观察它们的执行区别:
如果添加了 @Transactional注解就不会添加用户,因为程序报错了,它会自动回滚。如果没有@Transactional注解,就会添加用户,然后给前端报错,这是非常危险的。
Ⅰ、@Transactional 作用范围 🍓
@Transactional 可以用来修饰方法或类:
- 修饰方法时:需要注意只能应用到 public 方法上,否则不生效。推荐此种用法。
- 修饰类时:表明该注解对该类中所有的 public 方法都生效。
Ⅱ、@Transactional参数说明🍓
参数 | 作用 |
value | 当配置了多个事务管理器时,可以使用该属性指定选择哪个事务管理器. |
transactionManager | 当配置了多个事务管理器时,可以使用该属性指定选择哪个事务管理器. |
propagation | 事务的传播行为,默认值为Propagation.REQUIRED |
isolation | 事务的隔离级别.默认值为Isolation.DEFAULT |
timeout | 事务的超时时间,默认值为-1.如果超过该时间限制但事务还没有完成,则自动回滚事务. |
readOnly | 指定事务是否为只读事务,默认值为false;为了忽略那些不需要事务的方法,比如读取数据, 可以设置read-only为true. |
rollbackFor | 用于指定能够触发事务回滚的异常类型,可以指定多个异常类型. |
rollbackForClassName | 用于指定能够触发事务回滚的异常类型,可以指定多个异常类型. |
noRollbackFor | 抛出指定的异常类型,不回滚事务.也可以指定多个异常类型. |
noRollbackForClassName | 抛出指定的异常类型,不回滚事务,也可以指定多个异常类型. |
Ⅲ、注意事项🍓
@Transactional 在异常被捕获的情况下,不会进行事务自动回滚,验证以下代码是否会发生事务回滚:
@Transactional// 声明式事务(自动提交)
@RequestMapping("/insert")
public Integer insert(UserInfo userInfo) {
// 非空效验
if (userInfo == null || !StringUtils.hasLength(userInfo.getUsername()) ||
!StringUtils.hasLength(userInfo.getPassword())) {
return 0;
}
// 添加用户
int result = userService.add(userInfo);
try {
int num=10/0;
} catch (Exception e) {
System.out.println(e.getMessage());
}
return result;
}
数据库中也有了wangwu:
事务并没有进行回滚。
事务不会自动回滚解决方案🍓
①解决方案1🍒
对于捕获的异常,事务是会自动回滚的,因此解决方案1就是可以将异常重新抛出,具体实现如下:
@Transactional// 声明式事务(自动提交)
@RequestMapping("/insert")
public Integer insert(UserInfo userInfo) {
// 非空效验
if (userInfo == null || !StringUtils.hasLength(userInfo.getUsername()) ||
!StringUtils.hasLength(userInfo.getPassword())) {
return 0;
}
// 添加用户
int result = userService.add(userInfo);
try {
int num=10/0;
} catch (Exception e) {
System.out.println(e.getMessage());
throw e;
}
return result;
}
②解决方案2🍒
手动回滚事务,在方法中使用TransactionAspectSupport.currentTransactionStatus() 可 以得到当前的事务,然后设置回滚方法 setRollbackOnly 就可以实现回滚了,具体实现代码如下:
@Transactional// 声明式事务(自动提交)
@RequestMapping("/insert")
public Integer insert(UserInfo userInfo) {
// 非空效验
if (userInfo == null || !StringUtils.hasLength(userInfo.getUsername()) ||
!StringUtils.hasLength(userInfo.getPassword())) {
return 0;
}
// 添加用户
int result = userService.add(userInfo);
try {
int num=10/0;
} catch (Exception e) {
System.out.println(e.getMessage());
/*throw e;*/
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
return result;
}
Ⅳ、@Transactional 工作原理🍓
@Transactional 是基于 AOP 实现的,AOP又是使用动态代理实现的。如果目标对象实现了接口,默认情况下会采用JDK 的动态代理,如果目标对象没有实现了接口,会使用CGLIB 动态代理。
@Transactional 在开始执行业务之前,通过代理先开启事务,在执行成功之后再提交事务。如果中途遇到的异常,则回滚事务。
@Transactional 实现思路预览:
@Transactional 具体执行细节如下图所
三、事务隔离级别🍭
1、事务特性🍉
事务有4 ⼤特性(ACID),原子性、持久性、⼀致性和隔离性,具体概念如下:
- 原子性:⼀个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过⼀样。
- ⼀致性:在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作。
- 持久性:事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
- 隔离性:数据库允许多个并发事务同时对其数据进⾏读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不⼀致。事务隔离分为不同级别,包括读未提交 (
Read uncommitted
)、读提交 (read committed
)、可重复读 (repeatable read
) 和串行化 (Serializable
)。
原⼦性(Atomicity,或称不可分割性)⼀致性(Consistency)隔离性(Isolation,⼜称独立性)持久性(Durability)。
而这 4 种特性中,只有隔离性(隔离级别)是可以设置的。
为什么要设置事务的隔离级别?
设置事务的隔离级别是用来保障多个并发事务执行更可控,更符合操作者预期的。
什么是可控呢?
比如近几年比较严重的新冠病毒,我们会把直接接触到确证病例的人员隔离到酒店,而把间接接触者(和直接接触着但未确诊的人)隔离在自己的家中,也就是针对不同的人群,采取不同的隔离级别,这种隔离方式就和事务的隔离级别类似,都是采取某种行动让某个事件变的“更可控”。而事务的隔离级别就是为了防止,其他的事务影响当前事务执行的一种策略。