文章目录
Spring 事务传播机制使用和各种场景演示
对于以上事务传播机制,我们重点关注以下两个就可以了:
REQUIRED
(默认值)REQUIRES_NEW
REQUIRED(加入事务)
看下面代码实现:
- 用户注册,插入一条数据
- 记录操作日志,插入一条数据(出现异常)
观察propagation = Propagation.REQUIRED
的执行结果
@RequestMapping("/propaga")
@RestController
public class PropagationController {
@Autowired
private UserService userService;
@Autowired
private LogService logService;
public String r3(String name, String password) {
// 用户注册
userService.registryUser(name, password);
// 记录操作日志
logService.insertLog(name, "用户注册");
return "r3";
}
}
对应的 UserService
和 LogService
都添加上 @Transactional(propagation = Propagation.REQUIRED)
@Slf4j
@Service
public class UserService {
@Autowired
private UserInfoMapper userInfoMapper;
@Transactional(propagation = Propagation.REQUIRED)
public void registryUser(String name, String password) {
// 插入用户信息
userInfoMapper.insert(name, password);
}
}
@Slf4j
@Service
public class LogService {
@Autowired
private LogInfoMapper logInfoMapper;
@Transactional(propagation = Propagation.REQUIRED)
public void insertLog(String name, String op) {
int a = 10 / 0;
// 记录用户操作
logInfoMapper.insertLog(name, "用户注册");
}
}
运行程序:
但发现数据库没有插入任何数据
流程描述:
p1
方法开启事务- 用户注册,插入一条数据(执行成功)(和
p1
使用同一个事务) - 记录操作日志,插入一条数据(出现异常,执行失败)(和
p1
使用同一个事务) - 因为步骤 3 出现一次,事务回滚。步骤 2 和 3 使用同一个事务,所以步骤 2 的数据也回滚了
REQUIRES_NEW(新建事务)
将上述 UserService
和 LogService
中相关方法事务传播机制改为 Propagation.REQUIRES_NEW
@Slf4j
@Service
public class UserService {
@Autowired
private UserInfoMapper userInfoMapper;
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void registryUser(String name, String password) {
// 插入用户信息
userInfoMapper.insert(name, password);
}
}
@Slf4j
@Service
public class LogService {
@Autowired
private LogInfoMapper logInfoMapper;
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void insertLog(String name, String op) {
int a = 10 / 0;
// 记录用户操作
logInfoMapper.insertLog(name, "用户注册");
}
}
运行程序,发现用户数据插入成功了,日志表数据插入失败
LogService
方法中的事务不影响UserService
中的事务- 当我们不希望事务之间相互影响时,可以使用该传播行为
NEVER(不支持当前事务,抛异常)
修改 UserService
中对应方法的事务传播机制为 Propagation.NEVER
@Slf4j
@Service
public class UserService {
@Autowired
private UserInfoMapper userInfoMapper;
@Transactional(propagation = Propagation.NEVER)
public void registryUser(String name, String password) {
// 插入用户信息
userInfoMapper.insert(name, password);
}
}
程序执行报错,没有数据插入
NESTED(嵌套事务)
将上述 UserService
和 LogService
中相关方法事务传播机制改为 Propagation.NESTED
@Slf4j
@Service
public class UserService {
@Autowired
private UserInfoMapper userInfoMapper;
@Transactional(propagation = Propagation.NESTED)
public void registryUser(String name, String password) {
// 插入用户信息
userInfoMapper.insert(name, password);
}
}
@Slf4j
@Service
public class LogService {
@Autowired
private LogInfoMapper logInfoMapper;
@Transactional(propagation = Propagation.NESTED)
public void insertLog(String name, String op) {
int a = 10 / 0;
// 记录用户操作
logInfoMapper.insertLog(name, "用户注册");
}
}
运行程序,发现没有任何数据插入
流程描述:
Controller
中p1
方法开始事务UserService
用户注册,插入一条数据(嵌套p1
事务)LogService
记录操作日志,插入一条数据(出现一次,执行失败)(嵌套p1
事务,回滚当前事务,数据添加失败)- 由于是嵌套事务,
LogService
出现异常之后,往上找调用它的方法和事务,所以用户注册也失败了 - 最终结果是两个数据都没有添加
p1
事务可以认为是父事务,嵌套事务是子事务- 父事务出现异常,子事务也会回滚
- 子事务出现异常,如果不进行处理,也会导致父事务回滚
NESTED 和 REQUIRED 有什么区别
我们在 LogService
进行当前事务回滚,修改 LogService
,代码如下:
@Slf4j
@Service
public class LogService {
@Autowired
private LogInfoMapper logInfoMapper;
@Transactional(propagation = Propagation.NESTED)
public void insertLog(String name, String op) {
try {
int a = 10 / 0;
}catch (Exception e) {
// 回滚当前事务
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
// 记录用户操作
logInfoMapper.insertLog(name, "用户注册");
}
}
重新运行程序,发现用户表数据添加成功,日志表添加失败
LogService
中的事务已经回滚,但是嵌套事务不会回滚嵌套之前的事务,也就是说嵌套事务可以实现部分事务回滚
对比 REQUIRED
把 NESTED
传播机制改为 REQUIRED
,修改代码如下:
@Slf4j
@Service
public class UserService {
@Autowired
private UserInfoMapper userInfoMapper;
@Transactional(propagation = Propagation.REQUIRED)
public void registryUser(String name, String password) {
// 插入用户信息
userInfoMapper.insert(name, password);
}
}
@Slf4j
@Service
public class LogService {
@Autowired
private LogInfoMapper logInfoMapper;
@Transactional(propagation = Propagation.REQUIRED)
public void insertLog(String name, String op) {
try {
int a = 10 / 0;
}catch (Exception e) {
// 回滚当前事务
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
// 记录用户操作
logInfoMapper.insertLog(name, "用户注册");
}
}
重新运行程序,发现用户表和日志表的数据都添加失败了
REQUIRED
如果回滚就是回滚所有事务,不能实现部分事务的回滚(因为属于同一个事务)
NESTED 和 REQUIRED 区别
- 整个事务如果全部执行成功,二者的结果是一样的
- 如果事务一部分执行成功,
REQUIRED
加入事务会导致整个事务全部回滚。NESTED
嵌套事务可以实现局部回滚,不会影响上一个方法中执行的结果
嵌套事务之所以能够实现部分事务的回滚,是因为事务中有一个保存点(
savepoint
)的概念,其那套事务进入之后相当于新建了一个保存点,而回滚时只回滚到当前保存点
总结
Spring
中使用事务,有两种方式:编程式事务(手动操作)和声明式事务。其中声明式事务使用较多,在方法上添加@Transactional
即可实现- 通过
@Transactional(isolation = Isolation.SERIALIZABLE)
设置事务的隔离级别。Spring
中的事务隔离级别有 5 种 - 通过
@Transactional(propagation = Propagation.REQUIRED
设置事务的传播机制,Spring
中的事务传播级别有 7 种,重点关注REQUIRED
(默认值)和REQUIRES_NEW