【JavaEE】Spring(9):Spring事务
一、Spring实现事务
这里说的事务就是在数据库中提到的事务,本篇主要学习如何使用Spring实现事务
1.1 Spring声明式事务 @Transactional
通过@Transactional来操作事务
1. 添加依赖:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
</dependency>
2. 在需要的方法上添加 @Transactional ,这样在进入方法时就会开启事务,方法执行完成后自动提交事务,如果在方法执行的过程中出现异常,且该异常没有被捕获就会触发事务回滚
以代码为例
@Slf4j
@RequestMapping("/user")
@RestController
public class UserController {
@Autowired
private UserService userService;
@Transactional
@RequestMapping("/registry")
public String registry(String name, String password) throws IOException {
//用户注册
userService.registryUser(name, password);
log.info("用户数据插入成功");
//强制程序抛出异常
int a = 10/0;
return "注册成功";
}
}
我们在上述代码中故意写了异常,此时运行程序,就会触发事务回滚;虽然日志中显示用户数据插入成功,但实际上并未插入成功
@Transactional的作用
@Transactional可以修饰类和方法:
- 修饰 public 方法时 @Transactional才会生效,如果修饰的不是public方法则不会生效,但也不会报错
- 修饰类时,该类的所有public方法都会生效
前面说过程序中抛出异常,且异常没有被捕获,则会触发事务回滚
@Slf4j
@RequestMapping("/user")
@RestController
public class UserController {
@Autowired
private UserService userService;
@Transactional
@RequestMapping("/registry")
public String registry(String name, String password) throws IOException {
//用户注册
userService.registryUser(name, password);
log.info("用户数据插入成功");
try {
int a = 10/0;
} catch (Exception e) {
e.printStackTrace();
}
return "注册成功";
}
}
此时再运行程序,事务就会提交成功,不会触发事务回滚
那如果异常被捕获后,仍然想触发事务回滚该怎么办,有两种解决方案:
1. 重新抛出异常
try {
int a = 10/0;
} catch (Exception e) {
throw e;
}
2. 手动回滚事务
try {
int a = 10/0;
} catch (Exception e) {
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
通过 TransactionAspectSupport.currentTransactionStatus() 获取到当前事务,然后通过setRollbackOnly();回滚事务
二、 @Transactional 的三大属性
2.1 rollbackFor
rollbackFor可以指定触发事务回滚的异常类型,@Transactional默认只有运行时异常和Error时才会触发事务回滚,当抛出非运行时异常时,不会触发事务回滚
修改代码,使其抛出IOException(非运行时异常):
@Slf4j
@RequestMapping("/user")
@RestController
public class UserController {
@Autowired
private UserService userService;
@Transactional
@RequestMapping("/registry")
public String registry(String name, String password) throws IOException {
//用户注册
userService.registryUser(name, password);
log.info("用户数据插入成功");
if (true) {
throw new IOException();
}
return "注册成功";
}
}
运行程序,虽然抛出异常,但是事务提交成功
如果面对IOException这种非运行时异常,也想触发事务回滚,就可以通过配置rollbackFor来完成
@Slf4j
@RequestMapping("/user")
@RestController
public class UserController {
@Autowired
private UserService userService;
@Transactional(rollbackFor = Exception.class)
@RequestMapping("/registry")
public String registry(String name, String password) throws IOException {
//用户注册
userService.registryUser(name, password);
log.info("用户数据插入成功");
if (true) {
throw new IOException();
}
return "注册成功";
}
}
修改成上述代码后,程序中只要抛出Exception及其子类的异常,都会触发事务回滚
2.2 isolation
通过isolations属性可以设置事务的隔离级别
Spring中事务隔离级别有5种:
- Isolation.DEFAULT:以连接的数据库的事务隔离级别为主
- Isolation.READ_UNCOMMITTED:读未提交,对应SQL标准中 READ UNCOMMITTED
- Isolation.READ_COMMITTED:读已提交,对应SQL标准中 READ COMMITTED
- Isolation.REPEATABLE_READ:可重复读,对应SQL标准中 REPEATABLE READ
- Isolation.SERIALIZABLE:串行化,对应SQL标准中 SERIALIZABLE
@Transactional(isolation = Isolation.READ_COMMITTED);
2.3 事务传播机制
2.3.1 什么是事务传播机制
事务传播机制:当多个事务方法存在调用关系时,比如方法A的执行过程中需要调用方法B,此时方法B是加入A的事务还是创建自己的事务?事务传播机制就是处理这样的问题的
2.3.2 有哪些事务传播机制
- Propagation.REQUIRED:默认的事务传播级别,如果当前存在事务,则加入该事务,如果没有则新建一个事务
- Propagation.SUPPORTS:如果当前存在事务,则加入该事务,如果没有则以非事务的方式运行
- Propagation.MANDATORY:如果当前存在事务,则加入该事务,如果没有则抛出异常
- Propagation.REQUIRES_NEW:无论当前有没有事务,都新建一个事务,如果当前有事务则将当前这个事务挂起(不用)
- Propagation.NOT_SUPPORTED:以非事务的方式运行,如果当前存在事务,则将当前的事务挂起(不用)
- Propagation.NEVER:以非事务方式运行,如果当前存在事务,则抛出异常
- Propagation.NESTED:如果当前存在事务,则新建一个事务作为当前事务的嵌套事务来运行,如果当前没有事务,则该取值等价于Propagation.REQUIRED
2.3.3 事务传播机制代码演示
Propagation.REQUIRED
用户注册
@RequestMapping("/propaga")
@RestController
public class PropagationController {
@Autowired
private UserService userService;
@Autowired
private LogService logService;
@Transactional(propagation = Propagation.REQUIRED)
@RequestMapping("/p1")
public String r3(String name,String password){
//用户注册
userService.registryUser(name,password);
//记录操作⽇志
logService.insertLog(name,"用户注册");
return "r3";
}
}
用户注册包含插入用户数据和记录日志两个操作:
@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,"⽤⼾注册");
}
}
运行数据库,发现没有插入数据
流程描述:
1. p1方法开始事务
2. 用户注册,插入一条数据,执行成功(和p1使用同⼀个事务)
3. 记录操作日志,插⼊⼀条数据,出现异常,执行失败(和p1使用同⼀个事务)
4. 因为步骤3出现异常,事务回滚,步骤2和3使用同⼀个事务,所以步骤2的数据也回滚了
最终所有操作都没有执行成功
REQUIRES_NEW
将UserService和LogService中的方法的事务传播机制改为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事务
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,"⽤⼾注册");
}
}
运行程序,发现没有数据插入
流程描述:
1. Controller中p1方法开始事务
2. UserService用户注册,插入⼀条数据(嵌套p1事务)
3. LogService 记录操作日志,插⼊⼀条数据,出现异常,执行失败(嵌套p1事务,回滚当前事务,数据添加失败)
4. 由于是嵌套事务,LogService出现异常之后,往上找调⽤它的⽅法和事务,所以用户注册也失败了
5. 最终结果是两个数据都没有添加
p1事务可以认为是父事务,嵌套事务是子事务,父事务出现异常,子事务也会回滚,子事务出现异常,如果不进行处理,也会导致父事务回滚
NESTED可以实现部分事务回滚(捕获异常后,手动事务回滚):
@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,"⽤⼾注册");
}
}
此时就不会影响父事务
🙉本篇文章到此结束