这里的 事务 和之前 MySQL的事务 一样,都是表示将⼀组操作封装成⼀个执⾏单元(封装到⼀起),要么全部成功,要么全部失败。
Spring 中事务的实现
1. 编程式事务(手动档)。
package com.example.transactiondemo.controller;
import com.example.transactiondemo.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.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
// 编程式事务
@Autowired
private DataSourceTransactionManager transactionManager;
@Autowired
private TransactionDefinition transactionDefinition;
@RequestMapping("/del")
public int del(Integer id) {
if (id == null || id <= 0) return 0;
// 1.开启事务
TransactionStatus transactionStatus = null;
int result = 0;
try {
transactionStatus = transactionManager.getTransaction(transactionDefinition);
// 业务操作,删除用户
result = userService.del(id);
System.out.println("删除操作影响了:" + result + " 行");
// 2. 提交事务/回滚事务
transactionManager.commit(transactionStatus); // 提交事务
} catch (Exception e) {
if (transactionStatus != null) {
transactionManager.rollback(transactionStatus); // 出现异常,回滚事务
}
}
return result;
}
}
运行结果:
2. 声明式事务(自动挡)。
package com.example.transactiondemo.controller;
import com.example.transactiondemo.Service.UserService;
import org.apache.ibatis.annotations.Param;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/user2")
public class UserController2 {
@Autowired
private UserService userService;
@RequestMapping("del")
@Transactional // 在方法开始之前开启事务,方法正常执行结束之后提交事务,如果执行途中发生异常,则回滚事务
public int del(@Param("id") Integer id) {
if (id == null || id <= 0) {
return 0;
}
int result = userService.del(id);
System.out.println("删除操作影响了:" + result + " 行");
return result;
}
}
没有发生异常的情况下的运行结果:
1. 发生了异常情况下的运行结果(发生了回滚):
2. 但是这种代码的书写会导致程序直接异常而不能进行下一步了,为了更好解决这一问题就需要使用 try-catch 把错误代码包裹住:
第 1 种是将异常抛出去,然后 spring 感知到了就自动帮我们实现回滚了,第 2 种是自己手动处理,因为 spring 会认为我们既然都用了 try-catch 了,那么就是知道会有异常发生了,从而不会帮我们自动实现回滚。
关于 @Transactional
单元测试里:只要执行完都会进行回滚
普通类里:只有出现异常了才会进行回滚
参数的设置:
工作原理
通过切面,通过动态代理实现:
@Transactional 是基于 AOP 实现的,AOP ⼜是使⽤动态代理实现的。如果目标对象实现了接口,默认情况下会采用 JDK 的动态代理,如果⽬标对象没有实现了接⼝,会使用 CGLIB 动态代理。
@Transactional 在开始执行业务之前,通过代理先开启事务,在执行成功之后再提交事务。如果中途遇到的异常,则回滚事务。
设置事务隔离级别
Isolation.DEFAULT 是 spring 自己的,表示以连接的数据库的事务隔离级别为主。(Oracle 的默认隔离级别是 读已提交)
MySQL 事务隔离级别有 4 种:(默认的是 可重复读)
● 脏读:⼀个事务读取到了另⼀个事务修改的数据之后,后⼀个事务⼜进⾏了回滚操作,从⽽导致第⼀个事务读取的数据是错误的。
● 不可重复读:⼀个事务两次查询得到的结果不同,因为在两次查询中间,有另⼀个事务把数据修改了。
● 幻读:⼀个事务两次查询中得到的结果集不同,因为在两次查询中另⼀个事务有新增了⼀部分数据。
关于 spring boot 中事务会失效的情况
1.非public修饰的方法
2.timeout超时:当在 @Transactional 上,设置了一个较小的超时时间时,如果方法本身的执行时间超过了设置的 timeout 超时时间,那么就会导致本来应该正常插入数据的方法执行失败
3. 代码中有try/catch,也没进行手动回滚操作
4.当前数据库本身不支持事务
Spring 的事务传播机制
事务传播机制就是当有多个事务线性调用时,其中一个环节的事务发生回滚了,那么上一级的事务 要不要 发生回滚。
种类
1. Propagation.REQUIRED:默认的事务传播级别,它表示如果当前存在事务,则加入该事务;如果当前没有事务,则创建⼀个新的事务。
2. Propagation.SUPPORTS:如果当前存在事务,则加⼊该事务;如果当前没有事务,则以⾮事务的⽅式继续运⾏。
3. Propagation.MANDATORY:(mandatory:强制性)如果当前存在事务,则加⼊该事务;如果当前没有事务,则抛出异常。
4. Propagation.REQUIRES_NEW:表示创建⼀个新的事务,如果当前存在事务,则把当前事务挂起。也就是说不管外部⽅法是否开启事务,Propagation.REQUIRES_NEW 修饰的内部⽅法会新开启⾃⼰的事务,且开启的事务相互独⽴,互不⼲扰。
5. Propagation.NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
6. Propagation.NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
7. Propagation.NESTED:
如果当前存在事务,则创建⼀个事务作为当前事务的嵌套事务来运⾏;如果当前没有事务,则该取值等价于
PROPAGATION_REQUIRED。
根据是否支持当前事务分为 3 类
关于这三种的区别
- 加入事务,Propagation.REQUIRED:在方法被调用时会尝试加入当前的事务,如果不存在事务,则创建一个新的事务。如果外部事务回滚,那么内部事务也会被回滚。如果只是内部进行回滚,那么外部事务也会回滚但会报错。
- 嵌套事务,Propagation.NESTED:表示如果当前存在事务,则在嵌套事务中执行;如果当前没有事务,则创建一个新的事务并在其中执行。嵌套事务是独立于外部事务的子事务,它具有自己的保存点,并且可以独立于外部事务进行回滚。如果内部嵌套事务发生异常并回滚,它将会回滚到自己的保存点,而不影响外部事务。但如果外部的大事务回滚,会导致所有嵌套的小事务也回滚。和加入事务的区别就是有无保存点。
- Propagation.REQUIRES_NEW:在一个调用链上的事务各自执行,互不干扰。
举例子
假设有一个银行系统,其中包含转账的操作。
支持事务:
- 场景:用户 A 要向用户 B 转账 1000 元。
- 过程:如果这个转账操作支持事务,那么在整个事务执行过程中,要么所有相关的数据库操作(如减少用户 A 的账户余额、增加用户 B 的账户余额等)都成功完成,事务被提交,转账成功;要么如果在其中任何一个操作出现问题(例如数据库故障、余额不足等),整个事务都会回滚,所有的操作都被撤销,就好像转账从未发生过一样。
- 举例:在转账过程中,已经成功减少了用户 A 的余额,但在增加用户 B 的余额时出现了数据库错误。由于这是支持事务的操作,整个事务会回滚,用户 A 的余额会恢复到转账前的状态。
不支持事务:
- 场景:用户 A 要向用户 B 转账 1000 元。
- 过程:如果转账操作不支持事务,那么每个数据库操作都是独立的。即使其中一个操作失败,之前已经成功完成的操作也不会被回滚。
- 举例:在转账时先减少了用户 A 的余额,但增加用户 B 余额的操作失败了。由于不支持事务,用户 A 的余额已经减少,而用户 B 没有收到转账。
嵌套事务:
- 场景:假设在一个大的事务(如年度财务结算)中包含了用户 A 向用户 B 的转账这个小事务。
- 过程:在嵌套事务中,如果内部的转账小事务失败并回滚,不会影响外部的大事务。但如果外部的大事务回滚,会导致所有嵌套的小事务也回滚。
- 举例:年度财务结算事务中包含了多个转账操作,其中用户 A 向用户 B 的转账这个嵌套事务因为用户 B 的账户异常而回滚,不影响年度财务结算事务中的其他操作(小不影响大)。但如果年度财务结算事务因为某种原因整体回滚,那么包括这个转账在内的所有相关操作都会被撤销。(大会影响小)
总结
1. 在 Spring 项⽬中使⽤事务,⽤两种⽅法⼿动操作和声明式⾃动提交,其中后者使⽤的最多,在⽅法上添加 @Transactional 就可以实现了。
2. 设置事务的隔离级别 @Transactional(isolation = Isolation.SERIALIZABLE),Spring 中的事务隔离级别有 5 种。
3. 设置事务的传播机制 @Transactional(propagation = Propagation.REQUIRED),Spring 中的事务传播级别有 7 种。