当前位置: 首页 > article >正文

【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,"⽤⼾注册");
    }
}

此时就不会影响父事务


🙉本篇文章到此结束


http://www.kler.cn/a/535006.html

相关文章:

  • 【漫话机器学习系列】076.合页损失函数(Hinge Loss)
  • Redis --- 秒杀优化方案(阻塞队列+基于Stream流的消息队列)
  • axios如何利用promise无痛刷新token
  • ArrayList和Araay数组区别
  • 司库建设-融资需求分析与计划制定
  • ASP.NET Core Filter
  • 【YOLOv11改进- 注意力机制】YOLOv11+ACMix注意力机制(2021): 自注意力与卷积的聚合模块,助力YOLOv11有效涨点;
  • Apache SeaTunnel 整体架构运行原理
  • 【数据结构】循环链表
  • 最大矩阵的和
  • 《翻转组件库之发布》
  • Nexus简介及小白使用IDEA打包上传到Nexus3私服详细教程_ider2021 引用 nexus 上传
  • 怎么定义 vue-router 的动态路由?
  • 资源查找网址
  • es match 可查 而 term 查不到 问题分析
  • 前端开发知识梳理 - HTMLCSS
  • 202617读书笔记|《清溪俳句三百》——春有樱花,夏有蝉,秋有红叶,冬有雪
  • 寒假2.5
  • 【数据结构】(6) LinkedList 链表
  • 科技赋能数字内容体验的核心技术探索
  • 足球俱乐部管理系统的设计与实现
  • 【落羽的落羽 数据结构篇】单链表
  • Leetcode—340. 至多包含 K 个不同字符的最长子串【中等】Plus(力扣159变体罢了改个参数而已)
  • shell检测文件是windows格式还是unix
  • 智能门铃市场:开启智能家居新时代
  • linux中,软硬链接的作用和使用