Spring Boot事务管理详解:核心机制与应用场景解析
Spring Boot事务管理详解:核心机制与应用场景解析
1. 事务基础概念
1.1 什么是事务
事务是组操作的集合,是个不可分割的操作。事务会把所有的操作作为个整体,起向数据库提交或者是撤销操作请求。所以这组操作要么同时成功,要么同时失败。
1.2 为什么需要事务
假如有以下场景,若在存钱的过程中,A账户减少了100元,本应该就是在B账户中多出100元,但是如果没有事务,第一步执行成功了,第二步执行失败了,那么A 账户的100 元就凭空消失了。如果使用事务就可以解决这个问题,让这组操作要么全部成功,要么全部失败。
1.3 操作事务
在数据库中操作事务有以下几个步骤:
- 开启事务:start transaction/ begin (组操作前开启事务)
- 提交事务:commit (这组操作全部成功, 提交事务)
- 回滚事务:rollback (这组操作中间任何一个操作出现异常, 回滚事务)
当然这是在MySQL阶段学习的几个事务操作步骤,但是Spring也是有事务的操作的,那么我们接着往下面看看。
2. Spring事务管理
MySQL对于事务进行了实现,Spring对事务也是进行了实现,具体有两种实现方式:
- 编程式事务(手动编写代码操作事务)
- 声明式事务(利用注解自动开启和提交事务)
2.1 编程式事务
SpringBoot 内置了两个对象:
- DataSourceTransactionManager 事务管理器. 来获取事务(开启事务), 提交或回滚事务
- TransactionDefinition 是事务的属性, 在获取事务的时候需要将 TransactionDefinition 传递进去从获得个事务 TransactionStatus
具体的代码如下所示:
@RestController
@RequestMapping("/user")
public class UserController {
//手动开启事务
@Autowired
private DataSourceTransactionManager dataSourceTransactionManager;
@Autowired
private TransactionDefinition transactionDefinition;
@Autowired
private UserService userService;
@RequestMapping("/regist")
public boolean regist(String userName,String password){
//开启事务
TransactionStatus transactionStatus=
dataSourceTransactionManager.getTransaction(transactionDefinition);
//操作数据库
userService.registUser(userName,password);
//提交事务
dataSourceTransactionManager.commit(transactionStatus);
//回滚事务
dataSourceTransactionManager.rollback(transactionStatus);
return true;
}
}
解释:
注意这里要注入事务管理器对象,以及事务属性对象,这两个相当于是获取事务的状态,然后通过事务管理器通过提交或者回滚来操作我们的事务状态;
操作完数据库后,才进行事务的对应的处理哦~~~
2.2 声明式事务
这里的操作分为两步,主要式通过注解进行事务的自动开启:
- 添加依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
</dependency>
但是这里的依赖添加,这里小编添加了lombok,spring web,mybatis farmwork,mysql driver,并没有引入这里的依赖;
- 添加注解
在需要事务的方法上添加 @Transactional 注解就可以实现了。自动开启事务和提交事务,进方法时自动开启事务, 方法执行完会自动提交事务, 如果中途发生了没有处理的异常会自动回滚事务
代码如下所示:
@RestController
@RequestMapping("/trans")
public class TransController {
@Autowired
private UserService userService;
@Transactional
@RequestMapping("t1")
public Boolean register(String userName, String password){
Integer result = userService.registUser(userName, password);
System.out.println("插入用户表, result: "+ result);
return true;
}
}
当然,如果这里的存在异常情况下时,代码如下所示:
@Transactional
@RequestMapping("t1")
public Boolean register(String userName, String password){
Integer result = userService.registUser(userName, password);
System.out.println("插入用户表, result: "+ result);
int a = 10/0;
return true;
}
那么此时就会发生回滚,数据库就不会插入对应的数据;
但是对于所有发生了异常,此时事务都会进行回滚吗?当然不是,还要分为以下情况
- 异常捕获
当我们进行异常捕获后,代码如下所示
@RestController
@RequestMapping("/trans")
public class TransController {
@Autowired
private UserService userService;
/**
程序异常,捕获并处理,这里结果就是提交
*/
@Transactional
@RequestMapping("t1")
public Boolean register(String userName, String password){
Integer result = userService.registUser(userName, password);
System.out.println("插入用户表, result: "+ result);
try {
int a = 10/0;
}catch (Exception e){
//打印堆栈信息
e.printStackTrace();
}
return true;
}
}
解释:
这里就是通过try catch后进行堆栈信息的打印,所以最后就会进行事务的提交
(事务的提交与否,主要查看日志,是否存在commit的单词,或者去观察在数据库中数据是否进行了插入的操作)
- 异常捕获再次抛出
代码如下所示:
/**
捕获异常之后,没有处理,直接再次抛出,回滚
*/
@Transactional
@RequestMapping("t2")
public Boolean register1(String userName, String password){
Integer result = userService.registUser(userName, password);
System.out.println("插入用户表, result: "+ result);
try {
int a = 10/0;
}catch (Exception e){
throw e;
}
return true;
}
解释:
在捕获到这里的算数异常后,再次把异常进行抛出,那么此时就相当于是没有进行捕获的,那么事务就会进行回滚,并且这里还可以进行手动回滚;
在捕获异常后,添加:
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
此时就会进行手动的回滚操作;
- 编译时异常
代码如下所示:
/**
* 事务提交
*/
@Transactional
@RequestMapping("/t4")
public Boolean r4(String userName, String password) throws IOException {
Integer result = userService.registUser(userName, password);
System.out.println("插入用户表, result: "+ result);
if (true){
throw new IOException();//为编译时异常
}
return true;
}
解释:
在事务的提交中,只有运行时异常,以及Error会触发回滚,但是在这里的编译时异常是不可以回滚的(大致的可以理解为就是编译时异常有明显的报错,希望我们自己改正,这里事务就不管了)
当然还有一种情况
/**
* 事务回滚
*/
@Transactional
@SneakyThrows
@RequestMapping("/t5")
public Boolean r5(String userName, String password) {
Integer result = userService.registUser(userName, password);
System.out.println("插入用户表, result: "+ result);
if (true){
throw new IOException();
}
//在反编译中这里会捕获异常,并再次抛出
return true;
}
这里的事务结果就是回滚,为什么呢?这里添加了注解@SneakyThrows,我们在反编译文件中可以看到这里还进行了异常的捕获,但是没有处理,直接把异常抛出了,所以这里就是第二种情况
- 运行时异常
代码如下所示:
/**
* 事务回滚
*/
@Transactional
@RequestMapping("/t6")
public Boolean r6(String userName, String password) {
Integer result = userService.registUser(userName, password);
System.out.println("插入用户表, result: "+ result);
if (true){
//这里抛出的就是运行时异常,需要进行回滚
throw new RuntimeException();
}
return true;
}
这里的的事务那么就是直接回滚了,小编不再进行过多的赘述;
- @transaction属性
- rollbackFor: 异常回滚属性. 指定能够触发事务回滚的异常类型. 可以指定多个异常类型
- Isolation: 事务的隔离级别. 默认值为 Isolation.DEFAULT
- propagation: 事务的传播机制. 默认值为 Propagation.REQUIRED
注意:
@Transactional 默认只在遇到运行时异常和Error时才会回滚, 运行时异常不回滚. 即 Exception的类中, 除了RuntimeException及其子类其他的会不进行回滚
但是我们可以指定这里rollbackfor为所有异常类型,所以对于所有的异常,此时都会进行回滚的操作;代码如下所示:
@Transactional(rollbackFor = Exception.class, isolation = Isolation.DEFAULT)
@RequestMapping("/r7")
public Boolean r7(String userName, String password) throws IOException {
Integer result = userService.registUser(userName, password);
System.out.println("插入用户表, result: "+ result);
if (true){
throw new IOException();
}
return true;
}
- @transaction总结
@Transactional 可以来修饰方法或类:
修饰方法时: 只有修饰public 方法时才有效(修饰其他方法时不会报错, 也不生效)[推荐]
修饰类时: 对 @Transactional 修饰的类中所有的 public 方法都有效.
方法/类被 @Transactional 注解修饰时, 在调用方法执行开始之前, 会自动开启事务, 方法执行结束之后, 自动提交事务.
如果在方法执行过程中, 出现异常, 且异常未被捕获, 就进行事务回滚操作.
如果异常被程序捕获, 方法就被认为是成功执行, 依然会提交事务,但是捕获后重新抛出,就会回滚,若为运行时异常,那么也会进行回滚,但是编译时异常在不添加SneakyThrow时为提交;
3. 总结
本期主要讲解了关于事务,以及Spring事务的知识,对于Spring 事务的两种方式,小编都进行了代码的演示;