Spring事务与MySQL事务类似,都是将一组操作封装成一个执行单元,要么全部成功,要么全部失败。比如我们A给B转账100元,如果A账号成功扣款100元,但是B账号由于某种原因进款失败,那么A账号就相当于凭空失去了100元。使用事务就能够解决这个问题,整体执行一起成功或者失败。
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创建,此时我们多个对象中的设置可以不一样。
在实际开发中我们使用的是简单的注解来实现事务的开启提交。
特点:
使用注解的写法如下:
/**
* 声明式事务
* @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 | 抛出指定异常类型,可以多个 |
注意事项:
解决办法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;
}
与mysql事务隔离级别类似,但是新增了一个默认事务隔离级别,标识事务隔离级别与数据库设置一致。事务隔离级别是保证多个并发事务执⾏的可控性的(稳定性的)。主要有以下几类:
Spring事务隔离级别通过@Transactional ⾥的 isolation 属性设置;
使用Spring事务编写的代码在使用过程中可能存在相互调用的情况,如果传播链中有一个事务出错,那么如何解决呢,回不回滚上一个事务,如果多个事务回滚几个,事务传播机制就是定义了传播的固定模式。 事务隔离级别是保证多个并发事务执⾏的可控性的(稳定性的),⽽事务传播机制是保证⼀个事务在多个调⽤⽅法间的可控性的(稳定性的)。
Spring 事务传播机制包含以下 7 种:
以上事务传播机制可以分为下面三类:支持当前事务、不支持当前事务、嵌套事务
接下来我们创建一个添加日志的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是修改设置级别即可,此时我们会发现嵌套事务是不会回滚添加用户的,即使添加日志出错;