❤️❤️❤️Spring专栏更新中,各位大佬觉得写得不错,支持一下,感谢了!❤️❤️❤️ Spring + Spring MVC + MyBatis专栏
在Spring框架中,事务管理是一种用于维护数据库操作的一致性和完整性的机制。Spring事务管理提供了灵活的方式来处理事务,包括事务的创建、提交、回滚以及事务的传播行为。
事务定义:
将一组操作封装成一个执行单元(封装到⼀起),要么全部成功,要么全部失败。
为什么要用事务?
比如转账分为两个操作:
第一步操作:A 账户 -100 元。 第二步操作:B 账户 +100 元。
如果没有事务,第一步执行成功了,第二步执行失败了,那么 A 账户平白无故的 100 元就“人间蒸 发”了。而如果使用事务就可以解决这个问题,让这⼀组操作要么⼀起成功,要么⼀起失败。
Spring 中的事务操作分为两类:
在开始讲解它们之前,咱们先来回顾事务在 MySQL 中是如何使用的。
事务在 MySQL 有 3 个重要的操作:开启事务、提交事务、回滚事务,它们对应的操作命令如下:
-- 开启事务
start transaction;
-- 业务执行
-- 提交事务
commit;
回滚事务
rollback;
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这个用户。
从上述代码可以看出,以上代码虽然可以实现事务,但操作也很繁琐,有没有更简单的实现方法呢?请看下面声明式事务。
声明式事务的实现很简单,只需要在需要的方法上添加 @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 可以用来修饰方法或类:
参数 | 作用 |
---|---|
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 是基于 AOP 实现的,AOP又是使用动态代理实现的。如果目标对象实现了接口,默认情况下会采用JDK 的动态代理,如果目标对象没有实现了接口,会使用CGLIB 动态代理。
@Transactional 在开始执行业务之前,通过代理先开启事务,在执行成功之后再提交事务。如果中途遇到的异常,则回滚事务。
@Transactional 实现思路预览:
@Transactional 具体执行细节如下图所
事务有4 ⼤特性(ACID),原子性、持久性、⼀致性和隔离性,具体概念如下:
上⾯ 4 个属性,可以简称为ACID。
原⼦性(Atomicity,或称不可分割性) ⼀致性(Consistency) 隔离性(Isolation,⼜称独立性) 持久性(Durability)。
而这 4 种特性中,只有隔离性(隔离级别)是可以设置的。
为什么要设置事务的隔离级别?
设置事务的隔离级别是用来保障多个并发事务执行更可控,更符合操作者预期的。 什么是可控呢?
比如近几年比较严重的新冠病毒,我们会把直接接触到确证病例的人员隔离到酒店,而把间接接触者(和直接接触着但未确诊的人)隔离在自己的家中,也就是针对不同的人群,采取不同的隔离级别,这种隔离方式就和事务的隔离级别类似,都是采取某种行动让某个事件变的“更可控”。而事务的隔离级别就是为了防止,其他的事务影响当前事务执行的一种策略。