目录
1.事务的定义
2.Spring中事务的实现
2.1 MySQL中的事务使用
2.2 Spring中编程事务的实现
2.3 Spring中声明式事务
2.3.1 声明式事务的实现 @Transactional
2.3.2 @Transactional 作用域
2.3.3 @Transactional 参数说明
2.3.4 注意事项
(1)解决方法1(将异常抛出)
(2)解决方法2(使用代码手动回滚事务)
2.3.5 @Transactional 工作原理
3. 事务隔离级别
3.1 事务特性
3.2 Spring 中设置事务隔离级别
MySQL 事务隔离级别
Spring 事务隔离级别(5种)
注意事项:
4. Spring事务传播机制
4.1 事务传播机制是什么
4.2 为什么需要事务传播机制
4.3 事务传播机制有哪些
4.4 Spring 事务传播机制使用
4.4.1 支持当前事务(REQUIRED 默认)
4.4.2 嵌套事务(NESTED)
4.4.5 嵌套事务和REQUIRED事务的区别
事务定义:将一组操作封装成一个执行单元(封装到一起),要么一起成功,要么一起失败
那么我们为什么要用事务呢?
比方说银行的转账操作
如果没有事务,在A账户转账成功后,出现了失误和问题,B账户并没有加100,那么A账户便无故损失了100,如果使用了事务,当出现了失误和问题后便会立刻回滚给A账户,即要么一起成功,要么一起失败。
Spring中的事务操作分为两种:
MySQL 中事务有 3 个重要的操作:开启事务、提交事务、回滚事务,它们对应的操作命令如下:
-- 开启事务
start transaction;
-- 业务执⾏
-- 提交事务
commit;
-- 回滚事务
rollback;
Spring 手动操作事务和上述的 MySQL 操作事务类似,他也是有三个操作步骤:
@RestController
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;
}
// 开启事务(获取事务)
TransactionStatus transactionStatus = transactionManager.getTransaction(transactionDefinition);
int result = userService.add(userInfo);
System.out.println("add 受影响的行数:" + result);
// 提交事务
transactionManager.commit(transactionStatus);
// // 回滚事务
// transactionManager.rollback(transactionStatus);
return result;
}
}
因为回滚了事务,所以数据库中不会有wangwu这个用户
从上述代码可以看出,以上代码虽然可以实现事务,但操作也很繁琐,有没有更简单的实现方法呢?请看下面声明式事务。
声明式事务的实现,只需要在方法上添加 @Transactional 注解就可以实现,无需手动开启事务和提交事务,进入方法时自动开启事务,方法执行完全会自动提交事务,如果中途发生了没有处理的异常会自动回滚事务
// 声明式事务(自动提交)
@Transactional
@RequestMapping("/add2")
public int add2(UserInfo userInfo) {
if(userInfo==null || !StringUtils.hasLength(userInfo.getUsername())
|| !StringUtils.hasLength(userInfo.getPassword())) {
return 0;
}
int result = userService.add(userInfo);
System.out.println("add2 受影响的行数:" + result);
int num = 10/0;
return result;
}
我们可以看到在发生异常后事务会自动回滚,所有我们在数据库中无法找寻到添加数据,
发现数据库中无新增数据,这说明@Transactional生效了
@Transactional 可以用来修饰方法和类
参数 | 作用 |
---|---|
value | 当你配置多个事务管理器时,可以使用该属性指定选择用哪个事务管理器 |
transactionManager | 同上 |
propagation | 事务的传播行为,默认值为 Propagation.REQUIRED |
isolation | 事务的隔离级别,默认值为 Isolation.DEFAULT |
timeout | 事务的超时时间,默认值为-1,如果超过该时间限制但事务还没完成,则自动回滚事务 |
readOnly | 指定事务是否为只读事务,默认值为 false,为了忽略那些不需要事务的方法,比如读取数据可以设置 read-only 为 true |
rolibackFor | 用于指定能够触发事务回滚的异常类型,可以指定多个异常类型 |
rolibackForClassName | 同上 |
noRolibackFor | 抛出指定的异常类型,不回滚事务,也可以指定多个异常类型 |
noRollbackForClassName | 同上 |
@Transactional 在异常被捕获的情况下,不会进行事务自动回滚
@Transactional
@RequestMapping("/add2")
public int add3(UserInfo userInfo) {
if(userInfo==null || !StringUtils.hasLength(userInfo.getUsername())
|| !StringUtils.hasLength(userInfo.getPassword())) {
return 0;
}
int result = userService.add(userInfo);
System.out.println("add2 受影响的行数:" + result);
try {
int num = 10/0;
} catch (Exception e) {
}
return result;
}
这样是把异常打印出来了,且数据库中也添加进去了,但是出现了异常应该回滚才对的!
手动回滚事务,在⽅法中使⽤ TransactionAspectSupport.currentTransactionStatus() 可以得到当前的事务,然后设置回滚方法 setRollbackOnly 就可以实现回滚了,具体实现代码
@Transactional
@RequestMapping("/add3")
public int add3(UserInfo userInfo) {
if(userInfo==null || !StringUtils.hasLength(userInfo.getUsername())
|| !StringUtils.hasLength(userInfo.getPassword())) {
return 0;
}
int result = userService.add(userInfo);
System.out.println("add2 受影响的行数:" + result);
try {
int num = 10/0;
} catch (Exception e) {
//手动回滚事务
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
return result;
}
@Transactional 是基于 AOP 实现的,AOP ⼜是使⽤动态代理实现的。如果⽬标对象实现了接⼝,默认情况下会采⽤ JDK 的动态代理,如果⽬标对象没有实现了接⼝,会使⽤ CGLIB 动态代理。 @Transactional 在开始执⾏业务之前,通过代理先开启事务,在执⾏成功之后再提交事务。如果中途遇到的异常,则回滚事务。
@Transactional 实现思路:
@Transactional 具体执行细节:
事务有四大特性(ACID):原子性,持久性,一致性,隔离性
- 原子性:⼀个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态就像这个 事务从来没有执行过⼀样。
- 持久性:在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精准度、串联性以及后续数据库可以自发性地完成预定的工作
- 一致性:事务处理结束后,对数据的修改就是永久的。即使系统故障也不会丢失
- 隔离性:数据库允许多个并发事务同时对其数据进行读写和修改的能力。隔离性可以防止多个事务并发执行时由交叉执行而导致数据的不一致。
而这 4 种特性中,只有隔离性(隔离级别)是可以设置的
在设置事务隔离级别前我们想一下,为什么要设置事务隔离级别呢?
设置事务的隔离级别是用来保障多个并发事务执行更可控,更符合操作者预期的
这个可控表示的是,比如疫情的时候,有确诊、密接、次密接等针对不同的人群,采取不同的隔离级别,这种方式与事务的隔离级别类似,都是让某种行为操作变的 更可控,事务的隔离级别就是为了防止,其他事务影响当前事务执行的一种策略
事务隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
读未提交(READ UNCOMMITTED) | √ | √ | √ |
读已提交(READ COMMITTED) | √ | √ | |
可重复读(REPEATABLE READ) | √ | ||
串行化(SERIALIZABLE) |
- 当 Spring 中设置了事务隔离级别和连接的数据库(MySQL)事务隔离级别发送冲突的时候,以Spring为准
- Spring 中的事务隔离级别机制的实现是依靠连接数据库支持事务隔离级别为基础
Spring 事务传播机制:多个事务在相互调用时,事务是如何传递的
事务隔离级别是保证多个并发事务执行的可控性的(稳定性的),而事务传播机制是保证一个事务在多个调用方法间的可控性(稳定性的)
而事务传播机制解决的是⼀个事务在多个节点(方法)中传递的问题,如下图所示:
Spring 事务传播机制包含以下 7 种:
UserController:
@Autowired
private UserService userService;
@Autowired
private LogService logService;
@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);
if (result>0){
//日志
logService.add();
}
/*try {
int num = 10 / 0;
} catch (Exception e) {
System.out.println(e.getMessage());
*//*throw e;*//*
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}*/
return result;
}
UserService:
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
@Transactional(propagation = Propagation.REQUIRED)
public Integer add(UserInfo userInfo) {
int result = userMapper.add(userInfo);
System.out.println("用户添加:" + result);
return result;
}
}
LogService :
@RestController
public class LogService {
//捣乱,设置一个算数异常
@Transactional(propagation = Propagation.REQUIRED)
public int add() {
int num = 10 / 0;
return 1;
}
}
测试REQUIRED我们可以看到logservice中是有错误的,我们先添加了userService.add(userinfo)事务,当我们加入了log service事务出问题了,虽然之前已经添加成功了,但是应该也是回滚,如果都回滚那么就是ok的即add2没有添加数据库那么说明符合预期,当数据成功添加到数据库是就代表出错了
发现回滚了,这个就是加入事务,将自身变成整体的一部分,自身出错了那么整体就会出问题
执行流程描述:
- UserService中的保存方法正常执行
- LogService 保存日志程序报错,因为使用的是 Controller 中的事务,所以整个事务回滚。
- 数据库中没有插入任何数据,也就是步骤1中的用户插入方法也回滚了。
UserService:
@Controller
public class UserService {
@Autowired
private UserMapper userMapper;
@Transactional(propagation = Propagation.NESTED)
public Integer add(Userinfo userinfo){
int result = userMapper.add(userinfo);
System.out.println("用户添加:" + result);
return result;
}
}
UserController:
@Transactional(propagation = Propagation.NESTED)// 声明式事务(自动提交)
@RequestMapping("/add2")
public Integer add2(Userinfo userinfo){
// 非空校验
if (userinfo == null || !StringUtils.hasLength(userinfo.getUsername())
|| !StringUtils.hasLength(userinfo.getPassword())) {
return 0;
}
int result = userService.add(userinfo);
if (result > 0){
logService.add();
}
// System.out.println("add2:" + result);
return result;
}
LogService:
@Service
public class LogService {
@Transactional(propagation = Propagation.NESTED)
// @Transactional = @Transactional(propagation = Propagation.REQUIRED)
// @Transactional默认的是Propagation.REQUIRED
public int add(){
int num = 10/0;
return 1;
}
}
最终执行结果,用户表和日志表都没有添加任何数据。
发现嵌套事务也是没有问题的
但是当我们去让其手动回滚时:
LogService:
@Service
public class LogService {
@Transactional(propagation = Propagation.NESTED)
// @Transactional = @Transactional(propagation = Propagation.REQUIRED)
// @Transactional默认的是Propagation.REQUIRED
public int add(){
try {
int num = 10/0;
} catch (Exception e) {
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
return 1;
}
}
没有报错且添加成功没有回滚
这就是嵌套事务的特点,相当于logservice是临时工,出事了开了就行了,不会影响到整体
整个事务如果全部执行成功,二者的结果是⼀样的。
如果事务执行到一半失败了,那么加入事务整个事务会全部回滚;而嵌套事务会局部回滚,不会影响上一个方法中执行的结果