【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 的事务配置启用
相关推荐
BlockChain88811 小时前
SpringBoot实战一:10分钟搭建企业级用户管理系统(20000字完整项目)
java·spring boot·后端
拽着尾巴的鱼儿12 小时前
Springboot 缓存@Cacheable 使用
spring boot·后端·缓存
麦兜*12 小时前
SpringBoot Profile多环境配置详解,一套配置应对所有场景
java·数据库·spring boot
努力也学不会java12 小时前
【Spring Cloud】 服务注册/服务发现
人工智能·后端·算法·spring·spring cloud·容器·服务发现
像少年啦飞驰点、12 小时前
Java大厂面试真题:Spring Boot + Kafka + Redis 在电商场景下的实战应用
java·spring boot·redis·分布式·kafka·面试题·电商秒杀
ITUnicorn13 小时前
【Vue2+SpringBoot在线商城】13-本项目运用到的技术
java·spring boot·redis·后端
空空kkk13 小时前
Spring Boot项目的搭建
java·spring boot·后端
每天学习一丢丢13 小时前
Spring Boot 调用泛微 E9 Token 认证 + 创建流程完整教程
java·spring boot·后端
没有bug.的程序员13 小时前
Spring Boot 启动原理:从 @SpringBootApplication 到自动配置深度解析
java·spring boot·后端·python·springboot·application