文章目录
Spring事务与事务传播机制
前言
Spring事务与MySQL事务类似,都是将一组操作封装成一个执行单元,要么全部成功,要么全部失败。比如我们A给B转账100元,如果A账号成功扣款100元,但是B账号由于某种原因进款失败,那么A账号就相当于凭空失去了100元。使用事务就能够解决这个问题,整体执行一起成功或者失败。
1. Spring中事务的实现
1.1 编程式事务(手动编写代码操作事务)
SpringBoot 内置了两个对象,DataSourceTransactionManager ⽤来开启事务、提交、回滚事务的,TransactionDefinition 是事务的属性,在获取事务的时候需要将TransactionDefinition 传递进去从而获得⼀个事务 TransactionStatus,实现代码如下:
@RestController
public class UserController {
@Resource
private UserService userService;
// JDBC 事务管理器
@Resource
private DataSourceTransactionManager dataSourceTransactionManager;
// 定义事务属性
@Resource
private TransactionDefinition transactionDefinition;
@RequestMapping("/sava")
public Object save(User user) {
// 开启事务
TransactionStatus transactionStatus = dataSourceTransactionManager
.getTransaction(transactionDefinition);
// 插⼊数据库
int result = userService.save(user);
// 提交事务
dataSourceTransactionManager.commit(transactionStatus);
// // 回滚事务
// dataSourceTransactionManager.rollback(transactionStatus);
return result;
}
}
我们transactionmanager可以有多个,通过new创建,此时我们多个对象中的设置可以不一样。
在实际开发中我们使用的是简单的注解来实现事务的开启提交。
1.2 声明式事务(注解自动启动和提交事务)
特点:
- 使用@Transactional注解可以实现事务的自动提交,当程序没有报错时就会自动提交,当程序执行期间报错时会自动回滚事务。
- 可以添加在类和方法上。修饰方法时只能应用到public方法上,修饰类时标识该类的public方法都生效。
1.2.1 @Transactional的使用
使用注解的写法如下:
/**
* 声明式事务
* @param userinfo
* @return
*/
@Transactional //声明是事务自动提交
@RequestMapping("/insert")
public Integer insert(Userinfo userinfo){
// 非空校验
if (userinfo==null||!StringUtils.hasLength(userinfo.getPassword())||!StringUtils.hasLength(userinfo.getUsername())){
return 0;
}
int res = userService.add(userinfo);
return res;
}
使用该注解我们也有多种选择设置,通过该注解的参数实现。具体如下:
参数 | 作用 |
---|---|
value | 当配置多个事务管理器时,可以指定选择那个 |
transactionManeger | 当配置多个事务管理器时可以指定选哪个 |
propagation | 事务传播行为,默认为Propagation.REQUIRED |
isolation | 事务隔离级别,默认为Isolation.DEFAULT |
timeout | 事务超时时间,默认值为-1,如果超时还没完成,自动回滚事务 |
readOnly | 指定事务是否是只读事务,默认为false |
rollbackFor | 用于指定那个触发事务回滚的异常类型,可以指定多个 |
rollbackForClassNme | 用于指定那个触发事务回滚的异常类型,可以指定多个 |
noRollbackForClassName | 抛出指定异常类型,可以多个 |
noRollbackFor | 抛出指定异常类型,可以多个 |
注意事项:
- 我们使用注解时,如果有异常但是使用try-catch捕获异常,我们程序就不会回滚。
解决办法1:我们重新将异常再次抛出异常
/**
* 声明式事务
* @param userinfo
* @return
*/
@Transactional //声明是事务自动提交
@RequestMapping("/insert")
public Integer insert(Userinfo userinfo){
// 非空校验
if (userinfo==null||!StringUtils.hasLength(userinfo.getPassword())||!StringUtils.hasLength(userinfo.getUsername())){
return 0;
}
// 测试有异常时是否回滚,正常会回滚
// 如果有trycatch就不会回滚,
try {
int num= 10/0;
}catch (Exception e){
System.out.println(e.getMessage());
//此时再次抛出异常可以解决事务不会滚的问题
throw e;
}
int res = userService.add(userinfo);
return res;
}
解决办法2:手动回滚事务
/**
* 声明式事务
* @param userinfo
* @return
*/
@Transactional //声明是事务自动提交
@RequestMapping("/insert")
public Integer insert(Userinfo userinfo){
// 非空校验
if (userinfo==null||!StringUtils.hasLength(userinfo.getPassword())||!StringUtils.hasLength(userinfo.getUsername())){
return 0;
}
// 测试有异常时是否回滚,正常会回滚
// 如果有trycatch就不会回滚,
try {
int num= 10/0;
}catch (Exception e){
System.out.println(e.getMessage());
//此时再次抛出异常可以解决事务不会滚的问题
// throw e;
//手动回滚事务
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
int res = userService.add(userinfo);
return res;
}
1.2.2 @Transactional工作原理
- @Transactional 是基于 AOP 实现的,AOP 使⽤动态代理实现。如果⽬标对象实现了接⼝,默认情况下会采用 JDK 的动态代理,如果⽬标对象没有实现了接⼝,会使⽤ CGLIB 动态代理。
- @Transactional 在开始执行业务之前,通过代理先开启事务,在执行成功之后再提交事务。如果中途遇到的异常,则回滚事务。
2. Spring事务的隔离级别
与mysql事务隔离级别类似,但是新增了一个默认事务隔离级别,标识事务隔离级别与数据库设置一致。事务隔离级别是保证多个并发事务执⾏的可控性的(稳定性的)。主要有以下几类:
- Isolation.DEFAULT:以连接的数据库的事务隔离级别为主。
- Isolation.READ_UNCOMMITTED:读未提交,可以读取到未提交的事务,存在脏读。
- Isolation.READ_COMMITTED:读已提交,只能读取到已经提交的事务,解决了脏读,存在不可重复读。
- Isolation.REPEATABLE_READ:可重复读,解决了不可重复读,但存在幻读(MySQL默认级别)。
- Isolation.SERIALIZABLE:串⾏化,可以解决所有并发问题,但性能太低。
Spring事务隔离级别通过@Transactional ⾥的 isolation 属性设置;
3. 事务传播机制
使用Spring事务编写的代码在使用过程中可能存在相互调用的情况,如果传播链中有一个事务出错,那么如何解决呢,回不回滚上一个事务,如果多个事务回滚几个,事务传播机制就是定义了传播的固定模式。 事务隔离级别是保证多个并发事务执⾏的可控性的(稳定性的),⽽事务传播机制是保证⼀个事务在多个调⽤⽅法间的可控性的(稳定性的)。
3.1 事务传播机制分类
Spring 事务传播机制包含以下 7 种:
- Propagation.REQUIRED:默认的事务传播级别,它表示如果调用者存在事务,则被调用者加⼊该事务;如果调用者没有事务,则创建⼀个新的事务。
- Propagation.SUPPORTS:如果调用者存在事务,则被调用者加⼊该事务;如果没有事务,则以非事务的⽅式继续运⾏。
- Propagation.MANDATORY:如果调用者存在事务,则被调用者加⼊该事务;如果没有事务,则抛出异常。
- Propagation.REQUIRES_NEW:表示创建⼀个新的事务执行,如果调用者存在事务,则把该事务挂起。
- Propagation.NOT_SUPPORTED:以非事务方式运行,如果调用者存在事务,则把该事务挂起。
- Propagation.NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
- Propagation.NESTED:如果调用者存在事务,则被调用者创建⼀个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于 PROPAGATION_REQUIRED(即被调用者创建一个新的事务)。
以上事务传播机制可以分为下面三类:支持当前事务、不支持当前事务、嵌套事务
接下来我们创建一个添加日志的service,并且设置算是异常,当我们添加用户之后进行添加日志,两个事务都设置隔离级别
以下演示传播机制为REQUIRED的代码。可以发现当我们添加日志的事务报错时,我们添加用户的事务也会回滚:
Usercontroller类:
/**
* 事务传播机制的应用
* 添加用户时添加日志,事务传播机制为requried
* 我们添加日志事务有算数异常,
* 此时我们添加用户事务也会回滚,
* 因为相当于加入了事务变成一个事务
* 当传播机制为nested(嵌套时)
* 添加用户日志不会会滚
* @param userinfo
* @return
*/
@Transactional //声明是事务自动提交
@RequestMapping("/insert2")
public Integer insert2(Userinfo userinfo){
if (userinfo==null||!StringUtils.hasLength(userinfo.getPassword())||!StringUtils.hasLength(userinfo.getUsername())){
return 0;
}
// 添加用户
int res = userService.add(userinfo);
// 添加日志,
if(res>0){
logService.add();
}
return res;
}
LogService类:
package com.example.demo_tr.service;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
/**
* @author zq
* @date 2023-07-22 14:28
*/
@Service
public class LogService {
// 设置事务的传播机制,默认也为这个可以不写
@Transactional(propagation = Propagation.REQUIRED)
//@Transactional(propagation = Propagation.NESTED)
public int add(){
try {
int num=10/0;
}catch (Exception e){
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
return 1;
}
}
UserService类:
package com.example.demo_tr.service;
import com.example.demo_tr.entity.Userinfo;
import com.example.demo_tr.mapper.UserMapper;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
/**
* @author zq
* @date 2023-07-21 20:23
*/
@Service
public class UserService {
// @Autowired
@Resource
private UserMapper userMapper;
@Transactional(propagation = Propagation.REQUIRED)
//@Transactional(propagation = Propagation.NESTED)
public Integer add(Userinfo userinfo){
int res = userMapper.add(userinfo);
System.out.println("用户添加"+res);
return res;
}
}
我们可以通过访问地址,查看数据库user info表是否添加用户确定事务是否会滚;当事务传播级别为nested是修改设置级别即可,此时我们会发现嵌套事务是不会回滚添加用户的,即使添加日志出错;