【SpringBoot】Spring事务 && @Transactional详解 && Spring事务失效问题

文章目录

Ⅰ. Spring中事务的实现

Spring中的事务操作分为两类:

  1. 编程式事务(手动实现事务)
  2. 声明式事务(利用注解自动开启事务)

一、编程式事务(了解)

Spring 手动操作事务和 MySQL 操作事务类似,有三个重要操作步骤:

  1. 开启事务(获取事务)
  2. 提交事务
  3. 回滚事务

SpringBoot 内置了两个对象:

  1. DataSourceTransactionManager:事务管理器,用来获取事务(开启事务),提交或回滚事务的
  2. TransactionDefinition:事务的属性,在获取事务的时候需要将 TransactionDefinition 传递进去从而获得一个事务 TransactionStatus

代码实现:

java 复制代码
import com.example.demo.service.UserService;
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.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RequestMapping("/user")
@RestController
public class UserController {
    // 注入事务管理器
    @Autowired
    private DataSourceTransactionManager dataSourceTransactionManager;
    
    // 注入事务属性
    @Autowired
    private TransactionDefinition transactionDefinition;
    
    @Autowired
    private UserService userService;
    
    @RequestMapping("/registry")
    public String registry(String name,String password){
        // 开启事务
        TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);
        
        // 用户注册
        userService.registryUser(name,password);
        
        // 提交事务
        dataSourceTransactionManager.commit(transactionStatus);
        
        // 回滚事务
        //dataSourceTransactionManager.rollback(transactionStatus);
        return "注册成功";
    }
}

二、声明式事务:@Transactional

  1. 添加依赖

    xml 复制代码
       <dependency>
           <groupId>org.springframework</groupId>
           <artifactId>spring-tx</artifactId>
       </dependency>
  2. 在需要事务的方法上添加 @Transactional 注解就可以实现了。无需手动开启事务和提交事务,进入方法时自动开启事务,方法执行完会自动提交事务,如果中途发生了没有处理的异常会自动回滚事务

    java 复制代码
       @RequestMapping("/trans")
       @RestController
       public class TransactionalController {
           @Autowired
           private UserService userService;
           
           @Transactional
           @RequestMapping("/registry")
           public String registry(String name,String password){
               // 用户注册
               userService.registryUser(name,password);
               return "注册成功";
           }
       }

@Transactional 的作用

  • 修饰 方法 时:只有修饰 public 方法时才生效 (修饰其他方法时不会报错,也不生效)[推荐]
  • 修饰 时:对 @Transactional 修饰的类中所有的 public 方法都生效。

💥注意事项:

  • 如果在方法执行过程中,出现异常,且异常未被捕获,就进行事务回滚操作。
  • 如果异常被程序捕获,方法就被认为是成功执行,依然会提交事务。

下面对异常进行捕获:

java 复制代码
@Transactional
@RequestMapping("/registry")
public String registry(String name,String password){
    userService.registryUser(name,password); // 模拟用户注册功能
    log.info("用户数据插入成功");
    
    // 对异常进行捕获
    try {
        // 强制程序抛出异常
        int a = 10/0;
    }catch (Exception e){
        e.printStackTrace();
    }
    return "注册成功";
}

运行程序,发现虽然程序出错了,但是由于异常被捕获了所以事务依然得到了提交

如果需要事务进行回滚,有以下两种方式:

  1. 重新抛出异常

    java 复制代码
       @Transactional
       @RequestMapping("/registry")
       public String registry(String name,String password){
           userService.registryUser(name,password);
           log.info("用户数据插入成功");
           // 对异常进行捕获
           try {
               int a = 10/0;
           }catch (Exception e){
               // 将异常重新抛出去
               throw e;
           }
           return "注册成功";
       }
  2. 手动回滚事务 :使用 TransactionAspectSupport.currentTransactionStatus() 得到当前的事务,并使用 setRollbackOnly 设置回滚。

    java 复制代码
       @Transactional
       @RequestMapping("/registry")
       public String registry(String name,String password){
           userService.registryUser(name,password);
           log.info("用户数据插入成功");
           // 对异常进行捕获
           try {
               int a = 10/0;
           }catch (Exception e){
               // 手动回滚事务
               TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
           }
           return "注册成功";
       }

Ⅱ. @Transactional 详解

这里主要介绍注解中三个常见属性:

  1. rollbackFor:异常回滚属性。指定能够触发事务回滚的异常类型。可以指定多个异常类型
  2. Isolation:事务的隔离级别。默认值为 Isolation.DEFAULT
  3. Propagation:事务的传播机制。默认值为 Propagation.REQUIRED

一、异常回滚属性 rollbackFor

@Transactional 默认只在遇到 RuntimeException Error 时才会回滚 ,而非运行时异常不回滚

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

java 复制代码
// Controller 层
@RequestMapping("/r2")
public String r2(String name, String password) throws IOException {
    userService.registryUserWithTx(name, password);
    return "r2";
}

// Service 层
@Transactional(rollbackFor = Exception.class)
public void registryUserWithTx(String name, String password) throws IOException {
    userRepository.save(new User(name, password));
    log.info("用户数据插入成功");
    
    if (true) {
        throw new IOException(); // 模拟异常
    }
}

发现虽然程序抛出了异常,但是事务依然进行了回滚!

二、Spring事务隔离级别 Isolation

隔离级别 含义
Isolation.DEFAULT 以连接的数据库的事务隔离级别为主
Isolation.READ_UNCOMMITTED 读未提交
Isolation.READ_COMMITTED 读已提交
Isolation.REPEATABLE_READ 可重复读
Isolation.SERIALIZABLE 串行化

它的实现如下所示:

java 复制代码
public enum Isolation {
    DEFAULT(-1),
    READ_UNCOMMITTED(1),
    READ_COMMITTED(2),
    REPEATABLE_READ(4),
    SERIALIZABLE(8);
    
    private final int value;
    
    private Isolation(int value) {
        this.value = value;
    }
    
    public int value() {
        return this.value;
    }
}

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

java 复制代码
@Transactional(isolation = Isolation.READ_COMMITTED)
@RequestMapping("/r3")
public String r3(String name,String password) throws IOException {
    //... 代码省略
    return "r3";
}

三、Spring事务传播机制 Propagation

1. 什么是事务传播机制

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

比如有两个方法 AB 都被 @Transactional 修饰,A 方法调用 B 方法

A 方法运行时,会开启一个事务。当 A 调用 B 时,B 方法本身也有事务,此时 B 方法运行时,是加入 A 的事务呢,还是创建一个新的事务呢?

这就涉及到了事务的传播机制。

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

  • 事务传播机制 解决的是 一个事务在多个方法之间传递的问题

2. 事务的传播机制有哪些

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

传播机制 作用
Propagation.REQUIRED 默认的事务传播级别 。如果当前存在事务,则加入该事务 。如果当前没有事务,则创建一个新的事务
Propagation.SUPPORTS 如果当前存在事务,则加入该事务 。如果当前没有事务,则以非事务的方式继续运行
Propagation.MANDATORY 如果当前存在事务,则加入该事务 。如果当前没有事务,则抛出异常
Propagation.REQUIRES_NEW 如果当前存在事务,则把当前事务挂起 。也就是说不管外部方法是否开启事务,Propagation.REQUIRES_NEW 修饰的内部方法都会新开启自己的事务 且开启的事务相互独立,互不干扰
Propagation.NOT_SUPPORTED 以非事务方式运行 ,如果当前存在事务,则把当前事务挂起,不使用它。
Propagation.NEVER 以非事务方式运行 ,如果当前存在事务,则抛出异常
Propagation.NESTED 如果当前存在事务,则创建一个事务作为当前事务的子事务来运行 。如果当前没有事务,则创建一个新的事务

实现类如下所示:

java 复制代码
public enum Propagation {
    REQUIRED(0),
    SUPPORTS(1),
    MANDATORY(2),
    REQUIRES_NEW(3),
    NOT_SUPPORTED(4),
    NEVER(5),
    NESTED(6);
    
    private final int value;
    
    private Propagation(int value) {
        this.value = value;
    }
    
    public int value() {
        return this.value;
    }
}

💥nestedrequired 的区别

  • 整个事务如果全部执行成功,二者的结果是一样的。
  • 如果事务部分执行成功
    • REQUIRED 加入事务会导致整个事务全部回滚
    • NESTED 嵌套事务可以实现局部回滚,不会影响上一个方法中执行的结果。

嵌套事务之所以能够实现部分事务的回滚,是因为事务中有一个保存点(savepoint)的概念,嵌套事务进入之后相当于新建了一个保存点,而滚回时只回滚到当前保存点。

REQUIRED 是加入到当前事务中,并没有创建事务的保存点,因此出现了回滚就是整个事务回滚,这就是嵌套事务和加入事务的区别!

Ⅲ. Spring 事务的失效问题

一、调用方式不对(自调用)💥

  • 场景:在同一个类里,一个方法调用另一个加了 @Transactional 的方法。
  • 原因:Spring 事务是通过代理实现的,内部方法调用不会走代理,事务不会生效。
  • 解决:把需要事务的方法放到另一个类里,或通过 AopContext 获取代理调用。
    • 具体可以参考《黑马点评》中的优惠券一人一单问题

二、方法不是 public💥

  • Spring 默认只对 public 方法生效。
  • 如果 @Transactional 标注在 privateprotecteddefault 方法上,事务不会生效。

三、异常没抛出💥

  • 默认回滚规则:Spring 只会对 运行时异常(RuntimeException 或其子类) 回滚。

  • 如果你捕获异常后没抛出,事务不会回滚。

  • 如果抛的是受检异常(如 IOException),默认也不会回滚。

  • 解决:

    • 重新抛出运行时异常。

    • 或在注解里指定 rollbackFor

    java 复制代码
    @Transactional(rollbackFor = Exception.class)

四、数据库引擎不支持事务

  • MySQL 如果表引擎是 MyISAM,不支持事务,事务自然无效。
  • 必须用 InnoDB 才能支持。

五、多线程调用💥

  • 如果事务方法里开了新线程,线程里的操作不受当前事务控制。
  • 解决:避免在事务方法里直接用多线程,或者手动管理事务。

六、配置问题

  • @EnableTransactionManagement 没配置。
  • 事务管理器没有正确注入。

七、手动设置了传播属性

  • 如果事务传播属性设置不当(如 PROPAGATION_NEVER),也会导致失效。

✅ 快速排查清单

  1. 方法必须是 public
  2. 确保是代理调用(不是同类内调用)
  3. 确保异常能抛出,没被吞掉
  4. 数据库表必须支持事务
  5. 确认 Spring 的事务配置启用
相关推荐
jiayong232 小时前
知识库概念与核心价值01
java·人工智能·spring·知识库
To Be Clean Coder3 小时前
【Spring源码】getBean源码实战(三)
java·mysql·spring
㳺三才人子4 小时前
初探 Spring Framework OncePerRequestFilter
spring boot·spring·junit
这是程序猿4 小时前
基于java的ssm框架学生作业管理系统
java·开发语言·spring boot·spring·学生作业管理系统
小程故事多_805 小时前
Spring AI 赋能 Java,Spring Boot 快速落地 LLM 的企业级解决方案
java·人工智能·spring·架构·aigc
a187927218315 小时前
MySQL 事务
数据库·mysql·事务·mvcc·acid·readview·可见性判断算法
源码获取_wx:Fegn08955 小时前
基于springboot + vue小区人脸识别门禁系统
java·开发语言·vue.js·spring boot·后端·spring
廋到被风吹走6 小时前
【Spring】Spring AMQP 详细介绍
java·spring·wpf
海南java第二人6 小时前
Spring IOC依赖注入:从原理到实践的深度解析
spring·ioc