Java EE ----- Spring事务

发布于:2025-08-16 ⋅ 阅读:(16) ⋅ 点赞:(0)


特此注明 :
Designed By :长安城没有风
Version:1.0
Time:2025.08.09
Location:辽宁 · 阜新

📕1. 事务回顾

什么是事务?

事务是一组操作的集合,是一个不可分割的操作。事务会把所有的操作作为一个整体,一起向数据库提交或者是撤销操作请求,所以这组操作要么同时成功,要么同时失败。

为什么需要事务?

⽐如转账操作 :

第⼀步:A账⼾-100元
第⼆步:B账⼾+100元
如果没有事务,第⼀步执⾏成功了,第⼆步执⾏失败了,那么A账⼾的100元就平⽩⽆故消失了,如果使⽤事务就可以解决这个问题,让这⼀组操作要么⼀起成功,要么⼀起失败。

比如秒杀系统:

第⼀步:下单成功
第⼆步:扣减库存
下单成功后,库存也需要同步减少,如果下单成功,库存扣减失败,那么就会造成下单超出的情况。所以就需要把这两步操作放在同⼀个事务中,要么⼀起成功,要么⼀起失败。(理解事务概念为主,实际企业开发中,并不是简单的通过事务来处理。​)

事务的操作

事务的操作主要有三步:

  1. 开启事务:starttransaction/begin(⼀组操作前开启事务)
  2. 提交事务:commit(这组操作全部成功,提交事务)
  3. 回滚事务:rollback(这组操作中间任何⼀个操作出现异常,回滚事务)

📕2. Spring 中事务的实现​

✏️2.1 Spring 编程式事务(了解)​

Spring 编程式事务依赖以下核心接口:

  1. DataSourceTransactionManager : 事务管理器,专门用于处理基于 DataSource 的事务(如 JDBC、MyBatis 等持久层框架)。它实现了 PlatformTransactionManager 接口,核心功能是获取事务(开启事务)、提交事务、回滚事务,是编程式事务和声明式事务的底层核心组件。
  2. TransactionDefinition:定义事务属性(隔离级别、传播行为、超时时间、是否只读等)。
  3. TransactionStatus:表示当前事务的状态(是否活跃、是否已完成、是否需要回滚等)。
package xulong.com.captcha.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import xulong.com.captcha.mapper.UserMybatisPlusMapper;
import xulong.com.captcha.model.UserInfo;

@RestController
@RequestMapping("/user")
public class UserController {
    //事务管理器
    @Autowired
    private DataSourceTransactionManager dataSourceTransactionManager;
    //定义事务属性
    @Autowired
    private TransactionDefinition transactionDefinition;
    @Autowired
    private UserMybatisPlusMapper userMybatisPlusMapper;

    @PostMapping("/register")
    public UserInfo register(@RequestBody UserInfo userInfo) {
        //开启事务
        TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);
        try {
            //执行数据库操作
            userMybatisPlusMapper.insert(userInfo);
            //提交事务
            dataSourceTransactionManager.commit(transactionStatus);
        } catch (Exception e) {
            //回滚事务
            dataSourceTransactionManager.rollback(transactionStatus);
        }
        return userInfo;
    }
}


以上代码虽然可以实现事务,但是操作十分繁琐,各位了解即可,下面我们介绍声明式事务。

✏️2.2 Spring声明式事务

在需要事务的方法上添加 @Transactional 注解就可以实现了,无需手动开启事务和提交事务,进入方法时自动开启事务,方法执行完会自动提交事务,如果中途发生了没有处理的异常会自动回滚事务。如果异常被程序捕获,⽅法就被认为是成功执⾏,依然会提交事务。

package xulong.com.captcha.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import xulong.com.captcha.mapper.UserMybatisPlusMapper;
import xulong.com.captcha.model.UserInfo;

@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    private UserMybatisPlusMapper userMybatisPlusMapper;

    @PostMapping("/register")
    @Transactional
    public UserInfo register(@RequestBody UserInfo userInfo) {
        userMybatisPlusMapper.insert(userInfo);
        return userInfo;
    }
}

@Transactional 可以用来修饰方法或类 : ​

  1. 修饰⽅法时:只有修饰public⽅法时才⽣效(修饰其他⽅法时不会报错,也不⽣效)。
  2. 修饰类时:对 @Transactional 修饰的类中所有的public⽅法都⽣效。

运⾏程序,发现虽然程序出错了,但是由于异常被捕获了,所以事务依然得到了提交。如果需要事务进⾏回滚,有以下两种⽅式 :

重新抛出异常

package xulong.com.captcha.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import xulong.com.captcha.mapper.UserMybatisPlusMapper;
import xulong.com.captcha.model.UserInfo;

@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    private UserMybatisPlusMapper userMybatisPlusMapper;

    @PostMapping("/register")
    @Transactional
    public UserInfo register(@RequestBody UserInfo userInfo) {
        userMybatisPlusMapper.insert(userInfo);
        try{
            int i = 1/0;
        }catch (Exception e){
           throw new RuntimeException("注册失败");
        }
        return userInfo;
    }
}

手动回滚事务

使用 TransactionAspectSupport.currentTransactionStatus() 得到当前的事务,并设置setRollbackOnly。

package xulong.com.captcha.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import xulong.com.captcha.mapper.UserMybatisPlusMapper;
import xulong.com.captcha.model.UserInfo;

@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    private UserMybatisPlusMapper userMybatisPlusMapper;

    @PostMapping("/register")
    @Transactional
    public UserInfo register(@RequestBody UserInfo userInfo) {
        userMybatisPlusMapper.insert(userInfo);
        try{
            int i = 1/0;
        }catch (Exception e){
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        }
        return userInfo;
    }
}

📕3. @Transactional 详解​

✏️3.1 rollbackFor​

@Transactional 默认只在遇到运行时异常和Error时才会回滚,非运行时异常不回滚。即 Exception的子类中,除了RuntimeException及其子类。

在这里插入图片描述
如果我们需要所有异常都回滚,需要来配置@Transactional 注解当中的rollbackFor 属性,通过rollbackFor 这个属性指定出现何种异常类型时事务进行回滚。

package xulong.com.captcha.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import xulong.com.captcha.mapper.UserMybatisPlusMapper;
import xulong.com.captcha.model.UserInfo;

@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    private UserMybatisPlusMapper userMybatisPlusMapper;

    @PostMapping("/register")
    @Transactional(rollbackFor = Exception.class)
    public UserInfo register(@RequestBody UserInfo userInfo) {
        userMybatisPlusMapper.insert(userInfo);
        try{
            int i = 1/0;
        }catch (Exception e){
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        }
        return userInfo;
    }
}

结论:

  1. 在Spring的事务管理中,默认只在遇到运行时异常RuntimeException和Error时才会回滚。
  2. 如果需要回滚指定类型的异常,可以通过rollbackFor属性来指定。
✏️3.2 事务隔离级别​
🔖3.2.1 MySQL 事务隔离级别(回顾)​

SQL 标准定义了四种隔离级别, MySQL 全都支持. 这四种隔离级别分别是: ​

  1. 读未提交(READ UNCOMMITTED) : 读未提交,也叫未提交读。该隔离级别的事务可以看到其他事务中未提交的数据。

因为其他事务未提交的数据可能会发生回滚,但是该隔离级别却可以读到,我们把该级别读到的数据称之为脏数据,这个问题称之为脏读。

  1. 读已提交(READ COMMITTED) : 读已提交,也叫提交读。该隔离级别的事务能读取到已经提交事务的数据。

该隔离级别不会有脏读的问题,但由于在事务的执行中可以读取到其他事务提交的结果,所以在不同时间的相同 SQL 查询可能会得到不同的结果,这种现象叫做不可重复读​。

  1. 可重复读(REPEATABLE READ) : 事务不会读到其他事务对已有数据的修改,即使其他事务已提交。也就可以确保同一事务多次查询的结果一致,但是其他事务新插入的数据,是可以感知到的。这也就引发了幻读问题。可重复读,是 MySQL 的默认事务隔离级别。

比如此级别的事务正在执行时,另一个事务成功的插入了某条数据,但因为它每次查询的结果都是 一样的,所以会导致查询不到这条数据,自己重复插入时又失败(因为唯一约束的原因)。明明在事务中查询不到这条信息,但自己就是插入不进去,这个现象叫幻读。

  1. 串行化(SERIALIZABLE) : 序列化,事务最高隔离级别。它会强制事务排序,使之不会发生冲突,从而解决了脏读,不可重复读和幻读问题,但因为执行效率低,所以真正使用的场景并不多。

在这里插入图片描述

🔖3.2.2 Spring 事务隔离级别​

Spring 中事务隔离级别有5 种:​

  1. Isolation.DEFAULT :以连接的数据库的事务隔离级别为主。
  2. Isolation.READ_UNCOMMITTED :读未提交,对应SQL标准中 READ UNCOMMITTED
  3. Isolation.READ_COMMITTED :读已提交,对应SQL标准中 READ COMMITTED
  4. Isolation.REPEATABLE_READ :可重复读,对应SQL标准中 REPEATABLE READ
  5. Isolation.SERIALIZABLE :串⾏化,对应SQL标准中 SERIALIZABLE

Spring 中事务隔离级别可以通过 @Transactional 中的 isolation 属性进行设置​

@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    private UserMybatisPlusMapper userMybatisPlusMapper;

    @PostMapping("/register")
    @Transactional(rollbackFor = Exception.class,isolation = Isolation.REPEATABLE_READ)
    public UserInfo register(@RequestBody UserInfo userInfo) {
        return userInfo;
    }
}

事务隔离级别解决的是多个事务同时调⽤⼀个数据库的问题

在这里插入图片描述

📕4. Spring 事务传播机制​

✏️4.1 什么是事务传播机制

事务传播机制就是 : 多个事务方法存在调用关系时,事务是如何在这些方法间进行传播的。

举例说明:比如有两个方法A, B都被@Transactional 修饰,A方法调用B方法​。A方法运行时,会开启一个事务。当A调用B时,B方法本身也有事务,此时B方法运行时,是加入A的事务,还是创建一个新的事务呢?这个就涉及到了事务的传播机制。

⽐如公司流程管理,执⾏任务之前,需要先写执⾏⽂档,任务执⾏结束,再写总结汇报。
在这里插入图片描述
此时A部⻔有⼀项⼯作,需要B部⻔的⽀援,此时B部⻔是直接使⽤A部⻔的⽂档,还是新建⼀个⽂档呢?

事务传播机制解决的是一个事务在多个节点(方法)中传递的问题​

在这里插入图片描述

✏️4.2 事务的传播机制有哪些

@Transactional 注解支持事务传播机制的设置,通过 propagation 属性来指定传播行为。

Spring 事务传播机制有以下 7 种 :​

  1. Propagation.REQUIRED :默认的事务传播级别。如果当前存在事务,则加⼊该事务。如果当前没有事务,则创建⼀个新的事务。
  2. Propagation.SUPPORTS :如果当前存在事务,则加⼊该事务。如果当前没有事务,则以⾮事务的⽅式继续运⾏。
  3. Propagation.MANDATORY : 强制性。如果当前存在事务,则加⼊该事务,如果当前没有事务,则抛出异常。
  4. Propagation.REQUIRES_NEW :创建⼀个新的事务。如果当前存在事务,则把当前事务挂起。也就是说不管外部⽅法是否开启事务,Propagation.REQUIRES_NEW 修饰的内部⽅法都会新开启⾃⼰的事务,且开启的事务相互独⽴,互不⼲扰。
  5. Propagation.NOT_SUPPORTED :以⾮事务⽅式运⾏,如果当前存在事务,则把当前事务挂起(不⽤)。
  6. Propagation.NEVER :以⾮事务⽅式运⾏,如果当前存在事务,则抛出异常。
  7. Propagation.NESTED : 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行。如果当前没有事务,则该取值等价于 PROPAGATION_REQUIRED。

比如一对新人要结婚了, 关于是否需要房子​

  1. Propagation.REQUIRED :需要有房⼦。如果你有房,我们就⼀起住,如果你没房,我们就⼀起买房。(如果当前存在事务,则加⼊该事务。如果当前没有事务,则创建⼀个新的事务。)
  2. Propagation.SUPPORTS :可以有房⼦。如果你有房,那就⼀起住。如果没房,那就租房。(如果当前存在事务,则加⼊该事务。如果当前没有事务,则以⾮事务的⽅式继续运⾏。)
  3. Propagation.MANDATORY :必须有房⼦。要求必须有房,如果没房就不结婚。(如果当前存在事务,则加⼊该事务。如果当前没有事务,则抛出异常。)
  4. Propagation.REQUIRES_NEW :必须买新房,不管你有没有房,必须要两个⼈⼀起买房。即使有房也不住。(创建⼀个新的事务。如果当前存在事务,则把当前事务挂起。)
  5. Propagation.NOT_SUPPORTED :不需要房。不管你有没有房,我都不住,必须租房。(以⾮事务⽅式运⾏,如果当前存在事务,则把当前事务挂起。)
  6. Propagation.NEVER :不能有房⼦。(以⾮事务⽅式运⾏,如果当前存在事务,则抛出异常。)
  7. Propagation.NESTED :如果你没房,就⼀起买房。如果你有房,我们就以房⼦为根据地,做点⽣意。(如果如果当前存在事务,则创建⼀个事务作为当前事务的嵌套事务来运⾏。如果当前没有事务,则该取值等价于 PROPAGATION_REQUIRED )