【JAVA 进阶】SpringBoot 事务深度解析:从理论到实践的完整指南

文章目录

  • 前言
  • [第一章 事务基础:你必须掌握的核心概念](#第一章 事务基础:你必须掌握的核心概念)
    • [1.1 什么是事务?](#1.1 什么是事务?)
    • [1.2 事务的ACID特性](#1.2 事务的ACID特性)
      • [1.2.1 原子性(Atomicity)](#1.2.1 原子性(Atomicity))
      • [1.2.2 一致性(Consistency)](#1.2.2 一致性(Consistency))
      • [1.2.3 隔离性(Isolation)](#1.2.3 隔离性(Isolation))
      • [1.2.4 持久性(Durability)](#1.2.4 持久性(Durability))
    • [1.3 事务的并发问题](#1.3 事务的并发问题)
      • [1.3.1 脏读(Dirty Read)](#1.3.1 脏读(Dirty Read))
      • [1.3.2 不可重复读(Non-repeatable Read)](#1.3.2 不可重复读(Non-repeatable Read))
      • [1.3.3 幻读(Phantom Read)](#1.3.3 幻读(Phantom Read))
      • [1.3.4 丢失修改(Lost Update)](#1.3.4 丢失修改(Lost Update))
    • [1.4 数据库事务隔离级别](#1.4 数据库事务隔离级别)
      • [1.4.1 读未提交(Read Uncommitted)](#1.4.1 读未提交(Read Uncommitted))
      • [1.4.2 读已提交(Read Committed)](#1.4.2 读已提交(Read Committed))
      • [1.4.3 可重复读(Repeatable Read)](#1.4.3 可重复读(Repeatable Read))
      • [1.4.4 串行化(Serializable)](#1.4.4 串行化(Serializable))
      • [1.4.5 隔离级别与并发问题的关系](#1.4.5 隔离级别与并发问题的关系)
  • [第二章 SpringBoot事务的核心机制](#第二章 SpringBoot事务的核心机制)
    • [2.1 Spring事务管理的核心接口](#2.1 Spring事务管理的核心接口)
      • [2.1.1 PlatformTransactionManager](#2.1.1 PlatformTransactionManager)
      • [2.1.2 TransactionDefinition](#2.1.2 TransactionDefinition)
      • [2.1.3 TransactionStatus](#2.1.3 TransactionStatus)
    • [2.2 Spring事务的传播行为](#2.2 Spring事务的传播行为)
      • [2.2.1 REQUIRED(默认)](#2.2.1 REQUIRED(默认))
      • [2.2.2 SUPPORTS](#2.2.2 SUPPORTS)
      • [2.2.3 MANDATORY](#2.2.3 MANDATORY)
      • [2.2.4 REQUIRES_NEW](#2.2.4 REQUIRES_NEW)
      • [2.2.5 NOT_SUPPORTED](#2.2.5 NOT_SUPPORTED)
      • [2.2.6 NEVER](#2.2.6 NEVER)
      • [2.2.7 NESTED](#2.2.7 NESTED)
    • [2.3 SpringBoot事务的实现方式](#2.3 SpringBoot事务的实现方式)
      • [2.3.1 编程式事务](#2.3.1 编程式事务)
      • [2.3.2 声明式事务](#2.3.2 声明式事务)
  • [第三章 SpringBoot声明式事务的使用详解](#第三章 SpringBoot声明式事务的使用详解)
    • [3.1 @Transactional注解的核心属性](#3.1 @Transactional注解的核心属性)
      • [3.1.1 propagation:事务传播行为](#3.1.1 propagation:事务传播行为)
      • [3.1.2 isolation:事务隔离级别](#3.1.2 isolation:事务隔离级别)
      • [3.1.3 timeout:事务超时时间](#3.1.3 timeout:事务超时时间)
      • [3.1.4 readOnly:是否为只读事务](#3.1.4 readOnly:是否为只读事务)
      • [3.1.5 rollbackFor:指定需要回滚的异常类型](#3.1.5 rollbackFor:指定需要回滚的异常类型)
      • [3.1.6 noRollbackFor:指定不需要回滚的异常类型](#3.1.6 noRollbackFor:指定不需要回滚的异常类型)
    • [3.2 @Transactional注解的使用位置](#3.2 @Transactional注解的使用位置)
      • [3.2.1 用于类上](#3.2.1 用于类上)
      • [3.2.2 用于方法上](#3.2.2 用于方法上)
    • [3.3 事务的嵌套调用场景分析](#3.3 事务的嵌套调用场景分析)
      • [3.3.1 场景一:同一Service内的事务方法嵌套](#3.3.1 场景一:同一Service内的事务方法嵌套)
      • [3.3.2 场景二:不同Service间的事务方法嵌套(REQUIRED传播行为)](#3.3.2 场景二:不同Service间的事务方法嵌套(REQUIRED传播行为))
      • [3.3.3 场景三:不同Service间的事务方法嵌套(REQUIRES_NEW传播行为)](#3.3.3 场景三:不同Service间的事务方法嵌套(REQUIRES_NEW传播行为))
  • [第四章 SpringBoot事务的常见问题与解决方案](#第四章 SpringBoot事务的常见问题与解决方案)
    • [4.1 事务不生效的常见原因](#4.1 事务不生效的常见原因)
      • [4.1.1 方法不是public修饰的](#4.1.1 方法不是public修饰的)
      • [4.1.2 异常类型不匹配](#4.1.2 异常类型不匹配)
      • [4.1.3 异常被手动捕获且未重新抛出](#4.1.3 异常被手动捕获且未重新抛出)
      • [4.1.4 同一Service内的内部方法调用](#4.1.4 同一Service内的内部方法调用)
      • [4.1.5 数据源未配置事务管理器](#4.1.5 数据源未配置事务管理器)
      • [4.1.6 事务传播行为配置错误](#4.1.6 事务传播行为配置错误)
    • [4.2 事务回滚异常的处理技巧](#4.2 事务回滚异常的处理技巧)
      • [4.2.1 手动标记事务回滚](#4.2.1 手动标记事务回滚)
      • [4.2.2 自定义异常与事务回滚](#4.2.2 自定义异常与事务回滚)
    • [4.3 高并发场景下的事务优化](#4.3 高并发场景下的事务优化)
      • [4.3.1 缩小事务范围](#4.3.1 缩小事务范围)
      • [4.3.2 使用合适的隔离级别](#4.3.2 使用合适的隔离级别)
      • [4.3.3 避免事务中的锁竞争](#4.3.3 避免事务中的锁竞争)
      • [4.3.4 使用事务超时机制](#4.3.4 使用事务超时机制)
  • [第五章 总结与拓展](#第五章 总结与拓展)
    • [5.1 文章知识点总结](#5.1 文章知识点总结)
    • [5.2 知识拓展与延伸](#5.2 知识拓展与延伸)
    • [5.3 推荐阅读资料](#5.3 推荐阅读资料)

前言

在后端开发中,数据一致性是衡量系统可靠性的核心指标之一,而事务正是保障数据一致性的关键技术。无论是电商系统的订单支付、金融平台的资金流转,还是企业级应用的业务数据处理,都离不开事务的保驾护航。SpringBoot作为当前最流行的Java开发框架,其对事务的支持简洁高效,极大降低了开发者的使用成本。本文将从事务的基础理论出发,逐步深入SpringBoot事务的实现机制、使用方式、常见问题及优化方案,帮助开发者全面掌握SpringBoot事务知识,轻松应对实际开发中的数据一致性挑战。

第一章 事务基础:你必须掌握的核心概念

1.1 什么是事务?

事务(Transaction)是数据库操作的基本单元,它是由一组不可分割的数据库操作组成的逻辑集合。这组操作要么全部执行成功,要么全部执行失败,不存在部分执行的中间状态。例如,在电商下单场景中,需要完成"扣减库存""创建订单""扣减余额"三个操作,这三个操作就构成一个事务。如果其中任意一个操作失败,为了保证数据一致性,其他已执行的操作必须回滚,恢复到操作前的状态。

事务本质上是数据库提供的一种机制,主流的关系型数据库如MySQL、Oracle、PostgreSQL等都原生支持事务。而SpringBoot则是在数据库事务的基础上,通过Spring框架的封装,为开发者提供了更便捷、更灵活的事务管理能力。

1.2 事务的ACID特性

ACID是事务的四大核心特性,是衡量事务机制是否可靠的标准,也是理解事务本质的关键。这四个特性相互关联,共同保障数据的一致性。

1.2.1 原子性(Atomicity)

原子性是指事务中的所有操作是一个不可分割的整体,如同"原子"一样不可再分。事务的原子性要求:要么事务中的所有操作都执行成功并提交,要么所有操作都执行失败并回滚,不存在"部分成功、部分失败"的情况。

例如,银行转账业务中,"从A账户扣减1000元"和"向B账户增加1000元"是一个事务的两个操作。如果扣减操作成功但增加操作失败,原子性会保证扣减的1000元回滚到A账户,避免出现A账户资金减少但B账户资金未增加的错误数据。

1.2.2 一致性(Consistency)

一致性是指事务执行前后,数据库中的数据必须处于一个合法、一致的状态,即数据需要满足业务规则和完整性约束。一致性是事务的最终目标,其他三个特性都是为了保障一致性而存在的。

以电商库存管理为例,假设某商品的初始库存为100件。如果同时有两个订单分别购买10件和20件商品,那么两个事务执行完成后,库存应该变为70件,这就是数据的一致性。如果由于事务机制缺陷,导致最终库存为80件或90件,就破坏了数据的一致性。

1.2.3 隔离性(Isolation)

隔离性是指多个事务同时并发执行时,一个事务的执行过程不会被其他事务干扰,各个事务之间相互隔离,如同在独立的环境中执行一样。如果事务之间没有隔离性,多个并发事务操作同一批数据时,可能会出现脏读、不可重复读、幻读等问题,从而破坏数据一致性。

例如,事务A正在修改用户的余额,此时事务B读取该用户的余额。如果没有隔离性,事务B可能会读取到事务A修改过程中的中间数据,而这个数据最终可能因为事务A回滚而失效,导致事务B基于错误的数据进行业务处理。

1.2.4 持久性(Durability)

持久性是指事务一旦执行成功并提交,其对数据库中数据的修改就是永久性的,即使后续发生数据库崩溃、服务器宕机等故障,已提交的事务数据也不会丢失。数据库通常通过将事务日志写入磁盘来实现持久性,当系统故障恢复时,可以通过事务日志恢复已提交的事务数据。

例如,用户完成订单支付后,事务提交成功,此时即使数据库服务器突然断电,再次启动后,用户的支付记录和订单状态也不会丢失,确保业务数据的可靠。

1.3 事务的并发问题

在多用户并发访问数据库的场景下,如果事务的隔离性得不到保障,就会出现一系列并发问题。根据问题的严重程度,主要分为以下四类:

1.3.1 脏读(Dirty Read)

脏读是指一个事务读取到了另一个事务尚未提交的修改数据。由于未提交的事务可能会回滚,因此读取到的数据是"脏"的,即无效数据。

场景示例:事务A执行"扣减用户余额100元"操作,但未提交;此时事务B读取该用户的余额,得到扣减后的金额;随后事务A因为异常回滚,用户余额恢复为原始值,但事务B已经基于读取到的脏数据进行了后续业务处理(如创建订单),导致业务错误。

1.3.2 不可重复读(Non-repeatable Read)

不可重复读是指在同一个事务中,多次读取同一批数据时,得到的结果不一致。这种问题通常是由于在两次读取之间,有其他事务修改并提交了该数据。

场景示例:事务A第一次读取用户余额为1000元;此时事务B修改该用户余额为800元并提交;事务A再次读取该用户余额时,得到的结果变为800元,与第一次读取的结果不一致,导致事务A的业务逻辑出现混乱。

1.3.3 幻读(Phantom Read)

幻读是指在同一个事务中,多次执行相同的查询语句时,查询结果的行数不一致。这种问题通常是由于在两次查询之间,有其他事务插入或删除了符合查询条件的数据。

场景示例:事务A执行"查询余额大于500元的用户"操作,得到10条记录;此时事务B插入了一条余额为600元的用户数据并提交;事务A再次执行相同的查询语句,得到11条记录,如同出现了"幻觉"一样。

1.3.4 丢失修改(Lost Update)

丢失修改是指两个事务同时修改同一数据,后提交的事务会覆盖先提交的事务的修改结果,导致先提交的事务的修改丢失。

场景示例:事务A读取用户余额为1000元,计划修改为900元;同时事务B也读取该用户余额为1000元,计划修改为800元;事务A先提交,将余额改为900元;随后事务B提交,将余额改为800元,导致事务A的修改被丢失。

1.4 数据库事务隔离级别

为了解决事务的并发问题,数据库定义了不同的事务隔离级别。隔离级别越高,事务的并发问题越少,但数据库的并发性能也会越低。开发者需要根据业务场景在数据一致性和并发性能之间进行权衡。SQL标准定义了四个隔离级别,不同的数据库对这些隔离级别的支持程度有所不同。

1.4.1 读未提交(Read Uncommitted)

最低的隔离级别,允许一个事务读取另一个事务尚未提交的修改数据。该隔离级别无法解决脏读、不可重复读、幻读等任何并发问题,实际开发中很少使用。

1.4.2 读已提交(Read Committed)

允许一个事务读取另一个事务已经提交的修改数据。该隔离级别可以解决脏读问题,但无法解决不可重复读和幻读问题。这是大多数数据库的默认隔离级别,如Oracle、SQL Server等。

1.4.3 可重复读(Repeatable Read)

保证同一个事务中,多次读取同一批数据时,得到的结果一致。该隔离级别可以解决脏读和不可重复读问题,但无法完全解决幻读问题(MySQL的InnoDB引擎通过MVCC机制在可重复读级别下解决了幻读问题)。这是MySQL的默认隔离级别。

1.4.4 串行化(Serializable)

最高的隔离级别,要求所有事务串行执行,即一个事务执行完成后,另一个事务才能开始执行。该隔离级别可以解决所有并发问题,但会严重影响数据库的并发性能,适用于数据一致性要求极高但并发量极低的场景。

1.4.5 隔离级别与并发问题的关系

为了更清晰地展示隔离级别与并发问题的对应关系,以下表格进行了总结:

隔离级别 脏读 不可重复读 幻读 丢失修改
读未提交 可能 可能 可能 可能
读已提交 不可能 可能 可能 可能
可重复读 不可能 不可能 MySQL中不可能 不可能
串行化 不可能 不可能 不可能 不可能

第二章 SpringBoot事务的核心机制

2.1 Spring事务管理的核心接口

Spring框架的事务管理基于一系列核心接口实现,这些接口定义了事务管理的基本规范,SpringBoot作为Spring的子项目,完全继承了这些接口的功能,并通过自动配置简化了其使用。

2.1.1 PlatformTransactionManager

PlatformTransactionManager是Spring事务管理的核心接口,它定义了事务的基本操作方法,如获取事务、提交事务、回滚事务等。该接口是一个抽象层,不同的数据库或持久层框架有其对应的实现类,例如:

  • DataSourceTransactionManager:基于JDBC数据源的事务管理器,适用于使用JDBC或MyBatis进行数据访问的场景。

  • HibernateTransactionManager:基于Hibernate的事务管理器,适用于使用Hibernate进行数据访问的场景。

  • JpaTransactionManager:基于JPA的事务管理器,适用于使用JPA进行数据访问的场景。

在SpringBoot中,当引入spring-boot-starter-jdbc、spring-boot-starter-mybatis等依赖时,SpringBoot会自动配置对应的PlatformTransactionManager实现类,开发者无需手动配置。

2.1.2 TransactionDefinition

TransactionDefinition接口定义了事务的属性,如隔离级别、传播行为、超时时间、是否为只读事务等。这些属性决定了事务的执行规则,开发者可以通过配置这些属性来满足不同的业务需求。

2.1.3 TransactionStatus

TransactionStatus接口用于表示事务的当前状态,它提供了一系列方法来获取和修改事务的状态,如判断事务是否为新事务、是否已标记为回滚、设置事务回滚等。PlatformTransactionManager在执行事务操作时,会通过该接口获取事务的状态信息,并根据状态执行相应的操作。

2.2 Spring事务的传播行为

事务的传播行为是Spring事务管理中一个非常重要的概念,它定义了当一个带有事务的方法调用另一个带有事务的方法时,如何处理两个方法之间的事务关系。例如,是沿用当前事务,还是创建一个新的事务,或是将当前事务挂起等。Spring定义了7种事务传播行为,具体如下:

2.2.1 REQUIRED(默认)

如果当前存在事务,则加入该事务;如果当前不存在事务,则创建一个新的事务。这是最常用的传播行为,适用于大多数业务场景。例如,ServiceA的methodA方法调用ServiceB的methodB方法,如果methodA已经开启了事务,那么methodB会加入该事务;如果methodA没有开启事务,那么methodB会创建一个新的事务。

2.2.2 SUPPORTS

如果当前存在事务,则加入该事务;如果当前不存在事务,则以非事务方式执行。该传播行为适用于那些不需要事务支持,但如果有事务也可以加入的方法。例如,一些查询方法,即使没有事务也可以执行,但若存在事务则可以保证数据的一致性。

2.2.3 MANDATORY

如果当前存在事务,则加入该事务;如果当前不存在事务,则抛出异常。该传播行为强制要求方法必须在事务中执行,适用于那些必须依赖事务才能保证数据一致性的方法。例如,资金转账的核心方法,必须在事务中执行,否则直接抛出异常。

2.2.4 REQUIRES_NEW

无论当前是否存在事务,都创建一个新的事务;如果当前存在事务,则将当前事务挂起。该传播行为适用于那些需要独立事务的方法,即使调用者存在事务,被调用者的事务也独立执行,不受调用者事务回滚的影响。例如,订单创建成功后记录日志的方法,即使订单创建事务回滚,日志记录事务也应该正常提交。

2.2.5 NOT_SUPPORTED

以非事务方式执行;如果当前存在事务,则将当前事务挂起。该传播行为适用于那些不需要事务支持,且不希望干扰当前事务的方法。例如,一些耗时较长的非核心业务操作,如数据导出,以非事务方式执行可以提高性能,避免占用事务资源。

2.2.6 NEVER

以非事务方式执行;如果当前存在事务,则抛出异常。该传播行为强制要求方法必须在非事务环境中执行,适用于那些绝对不能在事务中执行的方法。例如,某些初始化方法,如果在事务中执行可能会导致数据锁定。

2.2.7 NESTED

如果当前存在事务,则在当前事务中创建一个嵌套事务;如果当前不存在事务,则创建一个新的事务。嵌套事务是当前事务的一个子事务,它依赖于当前事务,只有当前事务提交后,嵌套事务才能提交;如果当前事务回滚,嵌套事务也会回滚,但嵌套事务的回滚不会影响当前事务的其他部分。该传播行为与REQUIRES_NEW的区别在于,REQUIRES_NEW创建的是完全独立的事务,而NESTED创建的是依赖于父事务的子事务。

2.3 SpringBoot事务的实现方式

SpringBoot支持两种事务管理方式:编程式事务和声明式事务。编程式事务需要开发者手动编写代码来控制事务的开启、提交和回滚,灵活性高但代码侵入性强;声明式事务通过注解或XML配置的方式实现事务管理,代码侵入性低,是SpringBoot开发中推荐使用的方式。

2.3.1 编程式事务

编程式事务通过Spring提供的TransactionTemplate或直接使用PlatformTransactionManager来实现。TransactionTemplate是对编程式事务的封装,简化了事务操作的代码。

示例代码如下(基于TransactionTemplate):

Plain 复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionTemplate;

@Service
public class OrderService {

    @Autowired
    private OrderMapper orderMapper;
    
    @Autowired
    private StockMapper stockMapper;
    
    @Autowired
    private TransactionTemplate transactionTemplate;
    
    public void createOrder(Order order) {
        // 使用TransactionTemplate执行事务
        transactionTemplate.execute(status -> {
            try {
                // 扣减库存
                stockMapper.reduceStock(order.getProductId(), order.getQuantity());
                // 创建订单
                orderMapper.insertOrder(order);
                return true;
            } catch (Exception e) {
                // 事务回滚
                status.setRollbackOnly();
                e.printStackTrace();
                return false;
            }
        });
    }
}

编程式事务的优点是可以精确控制事务的边界和执行逻辑,适用于复杂的事务场景;缺点是需要在业务代码中嵌入事务管理代码,增加了代码的耦合度。

2.3.2 声明式事务

声明式事务基于AOP(面向切面编程)实现,通过注解或XML配置的方式将事务管理逻辑与业务逻辑分离,开发者只需在需要事务支持的方法上添加注解即可实现事务管理。SpringBoot推荐使用@Transactional注解实现声明式事务。

使用声明式事务的步骤非常简单:

  1. 在SpringBoot启动类上添加@EnableTransactionManagement注解(SpringBoot 2.0+版本可省略该注解,因为其会自动识别事务相关依赖并开启事务管理)。

  2. 在需要事务支持的Service方法上添加@Transactional注解。

示例代码如下:

Plain 复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class OrderService {

    @Autowired
    private OrderMapper orderMapper;
    
    @Autowired
    private StockMapper stockMapper;
    
    // 添加@Transactional注解开启事务
    @Transactional
    public void createOrder(Order order) {
        // 扣减库存
        stockMapper.reduceStock(order.getProductId(), order.getQuantity());
        // 创建订单
        orderMapper.insertOrder(order);
        // 如果出现异常,事务会自动回滚
        if (order.getQuantity() <= 0) {
            throw new IllegalArgumentException("订单数量不能小于等于0");
        }
    }
}

声明式事务的优点是代码侵入性低,事务管理逻辑与业务逻辑分离,提高了代码的可读性和可维护性;缺点是事务控制的粒度相对较粗,无法精确控制事务的执行逻辑。

第三章 SpringBoot声明式事务的使用详解

3.1 @Transactional注解的核心属性

@Transactional注解提供了多个属性,用于配置事务的传播行为、隔离级别、超时时间等,开发者可以根据业务需求灵活配置。以下是常用的核心属性:

3.1.1 propagation:事务传播行为

用于指定事务的传播行为,默认值为Propagation.REQUIRED。可以通过Propagation枚举类指定其他传播行为,例如:

Plain 复制代码
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void logOrderCreate(Order order) {
    // 记录订单创建日志,该方法会创建新的事务
    logMapper.insertOrderLog(order.getId(), "订单创建成功");
}

3.1.2 isolation:事务隔离级别

用于指定事务的隔离级别,默认值为Isolation.DEFAULT,即使用数据库的默认隔离级别。可以通过Isolation枚举类指定其他隔离级别,例如:

Plain 复制代码
@Transactional(isolation = Isolation.REPEATABLE_READ)
public List<Order> queryOrderByUserId(Long userId) {
    // 查询用户的订单,使用可重复读隔离级别
    return orderMapper.selectByUserId(userId);
}

3.1.3 timeout:事务超时时间

用于指定事务的超时时间,单位为秒,默认值为-1,表示不设置超时时间(即使用数据库的默认超时时间)。如果事务执行时间超过指定的超时时间,事务会自动回滚。例如:

Plain 复制代码
@Transactional(timeout = 30)
public void batchImportData(List<Data> dataList) {
    // 批量导入数据,事务超时时间为30秒
    dataMapper.batchInsert(dataList);
}

3.1.4 readOnly:是否为只读事务

用于指定事务是否为只读事务,默认值为false。如果将该属性设置为true,数据库会对事务进行优化,提高查询性能,但此时事务中不能执行修改数据的操作(如插入、更新、删除),否则会抛出异常。该属性适用于纯查询的业务方法,例如:

Plain 复制代码
@Transactional(readOnly = true)
public Order queryOrderById(Long orderId) {
    // 纯查询方法,设置为只读事务
    return orderMapper.selectById(orderId);
}

3.1.5 rollbackFor:指定需要回滚的异常类型

默认情况下,Spring事务只在遇到运行时异常(RuntimeException)和错误(Error)时才会回滚事务,对于编译时异常不会回滚。通过rollbackFor属性可以指定需要回滚的异常类型,例如:

Plain 复制代码
@Transactional(rollbackFor = Exception.class)
public void updateOrderStatus(Long orderId, Integer status) throws Exception {
    // 该方法抛出任何Exception类型的异常,事务都会回滚
    Order order = orderMapper.selectById(orderId);
    if (order == null) {
        throw new Exception("订单不存在");
    }
    order.setStatus(status);
    orderMapper.update(order);
}

3.1.6 noRollbackFor:指定不需要回滚的异常类型

与rollbackFor属性相反,noRollbackFor属性用于指定不需要回滚的异常类型,即使该异常是运行时异常,事务也不会回滚。例如:

Plain 复制代码
@Transactional(noRollbackFor = BusinessException.class)
public void processOrder(Order order) {
    try {
        // 业务处理逻辑
    } catch (BusinessException e) {
        // 抛出BusinessException时,事务不回滚
        e.printStackTrace();
    }
}

3.2 @Transactional注解的使用位置

@Transactional注解可以用于类上或方法上,其作用范围有所不同:

3.2.1 用于类上

当@Transactional注解用于类上时,该注解会作用于类中的所有公共(public)方法,即类中的所有public方法都会开启事务。如果类中的某个方法需要特殊的事务配置,可以在该方法上单独添加@Transactional注解,方法上的注解会覆盖类上的注解配置。例如:

Plain 复制代码
@Service
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public class OrderService {

    // 继承类上的事务配置
    public void createOrder(Order order) {
        // 业务逻辑
    }
    
    // 覆盖类上的事务配置,使用REQUIRES_NEW传播行为
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void logOrderCreate(Order order) {
        // 业务逻辑
    }
}

3.2.2 用于方法上

当@Transactional注解用于方法上时,该注解只作用于当前方法。需要注意的是,@Transactional注解只能作用于公共(public)方法,对于非public方法(如private、protected、default修饰的方法),注解不会生效,因为Spring AOP只能代理public方法。

3.3 事务的嵌套调用场景分析

在实际开发中,经常会遇到事务方法嵌套调用的场景,不同的传播行为会导致不同的事务执行结果。以下通过几个典型场景分析事务的嵌套调用逻辑:

3.3.1 场景一:同一Service内的事务方法嵌套

在同一Service类中,带有事务的methodA方法调用带有事务的methodB方法,此时由于Spring AOP的代理机制,methodB方法上的@Transactional注解不会生效,事务的传播行为由methodA方法的配置决定。例如:

Plain 复制代码
@Service
public class OrderService {

    @Transactional(propagation = Propagation.REQUIRED)
    public void methodA(Order order) {
        // 调用同一类中的methodB方法
        methodB(order);
        // 业务逻辑
    }
    
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void methodB(Order order) {
        // 业务逻辑
    }
}

上述代码中,methodA调用methodB时,由于是内部方法调用,没有通过Spring的代理对象,因此methodB上的@Transactional注解不会生效,methodB会加入methodA的事务中,而不会创建新的事务。如果需要methodB的事务注解生效,可以通过以下两种方式解决:

  1. 将methodB方法提取到另一个Service类中,通过依赖注入的方式调用。

  2. 在当前Service类中通过@Autowired注入自身的代理对象,使用代理对象调用methodB方法。

方式二的示例代码如下:

Plain 复制代码
@Service
public class OrderService {

    @Autowired
    private OrderService orderService; // 注入自身的代理对象
    
    @Transactional(propagation = Propagation.REQUIRED)
    public void methodA(Order order) {
        // 使用代理对象调用methodB方法
        orderService.methodB(order);
        // 业务逻辑
    }
    
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void methodB(Order order) {
        // 业务逻辑
    }
}

3.3.2 场景二:不同Service间的事务方法嵌套(REQUIRED传播行为)

ServiceA的methodA方法(REQUIRED传播行为)调用ServiceB的methodB方法(REQUIRED传播行为),此时methodB会加入methodA的事务中,两者共用一个事务。如果methodA或methodB中出现异常,整个事务会回滚。例如:

Plain 复制代码
@Service
public class ServiceA {

    @Autowired
    private ServiceB serviceB;
    
    @Transactional(propagation = Propagation.REQUIRED)
    public void methodA() {
        // 业务逻辑A
        serviceB.methodB();
        // 如果此处抛出异常,methodA和methodB的操作都会回滚
        throw new RuntimeException("methodA异常");
    }
}

@Service
public class ServiceB {

    @Transactional(propagation = Propagation.REQUIRED)
    public void methodB() {
        // 业务逻辑B
    }
}

上述代码中,methodA调用methodB后抛出异常,由于两者共用一个事务,因此methodA和methodB的操作都会回滚。

3.3.3 场景三:不同Service间的事务方法嵌套(REQUIRES_NEW传播行为)

ServiceA的methodA方法(REQUIRED传播行为)调用ServiceB的methodB方法(REQUIRES_NEW传播行为),此时methodB会创建一个新的事务,与methodA的事务相互独立。如果methodA中出现异常,methodA的事务会回滚,但methodB的事务不会受影响;如果methodB中出现异常,methodB的事务会回滚,同时异常会传播到methodA,导致methodA的事务也回滚。例如:

Plain 复制代码
@Service
public class ServiceA {

    @Autowired
    private ServiceB serviceB;
    
    @Transactional(propagation = Propagation.REQUIRED)
    public void methodA() {
        // 业务逻辑A
        serviceB.methodB();
        // 如果此处抛出异常,methodA的操作回滚,methodB的操作已提交不会回滚
        throw new RuntimeException("methodA异常");
    }
}

@Service
public class ServiceB {

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void methodB() {
        // 业务逻辑B,执行完成后会提交事务
    }
}

上述代码中,methodB执行完成后会提交自己的事务,随后methodA抛出异常,methodA的事务回滚,但methodB的事务已经提交,因此methodB的操作不会回滚。

第四章 SpringBoot事务的常见问题与解决方案

4.1 事务不生效的常见原因

在使用SpringBoot事务的过程中,经常会遇到事务不生效的问题,即方法执行出现异常后,事务没有回滚。以下是事务不生效的常见原因及解决方案:

4.1.1 方法不是public修饰的

Spring AOP只能代理public方法,@Transactional注解只对public方法生效。如果将注解添加到非public方法上,事务不会生效。

解决方案:将需要事务支持的方法改为public修饰。

4.1.2 异常类型不匹配

默认情况下,Spring事务只在遇到RuntimeException和Error时才会回滚,如果方法抛出的是编译时异常(如IOException、SQLException等),事务不会回滚。

解决方案:通过@Transactional注解的rollbackFor属性指定需要回滚的异常类型,例如rollbackFor = Exception.class。

4.1.3 异常被手动捕获且未重新抛出

如果在事务方法中手动捕获了异常,并且没有将异常重新抛出,Spring无法感知到异常的发生,因此不会触发事务回滚。例如:

Plain 复制代码
@Transactional
public void createOrder(Order order) {
    try {
        stockMapper.reduceStock(order.getProductId(), order.getQuantity());
        orderMapper.insertOrder(order);
    } catch (Exception e) {
        // 捕获异常但未重新抛出,事务不会回滚
        e.printStackTrace();
    }
}

解决方案:在catch块中重新抛出异常,或者通过TransactionStatus的setRollbackOnly()方法手动标记事务回滚。修改后的代码如下:

Plain 复制代码
@Transactional
public void createOrder(Order order) {
    try {
        stockMapper.reduceStock(order.getProductId(), order.getQuantity());
        orderMapper.insertOrder(order);
    } catch (Exception e) {
        e.printStackTrace();
        // 重新抛出异常,触发事务回滚
        throw new RuntimeException("创建订单失败", e);
    }
}

4.1.4 同一Service内的内部方法调用

如3.3.1节所述,同一Service类中,事务方法调用另一个事务方法时,由于是内部方法调用,没有通过Spring的代理对象,因此被调用方法上的@Transactional注解不会生效。

解决方案:将被调用的事务方法提取到另一个Service类中,或者在当前Service类中注入自身的代理对象进行调用。

4.1.5 数据源未配置事务管理器

SpringBoot需要为数据源配置对应的事务管理器(如DataSourceTransactionManager)才能实现事务管理。如果数据源没有配置事务管理器,事务不会生效。

解决方案:确保项目中引入了对应的数据源依赖(如spring-boot-starter-jdbc),SpringBoot会自动配置事务管理器。如果是自定义数据源,需要手动配置事务管理器,示例代码如下:

Plain 复制代码
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;

import javax.sql.DataSource;

@Configuration
public class TransactionConfig {

    @Bean
    public PlatformTransactionManager transactionManager(DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }
}

4.1.6 事务传播行为配置错误

如果事务传播行为配置不当,也可能导致事务不生效。例如,将传播行为配置为NOT_SUPPORTED或NEVER,会导致方法以非事务方式执行。

解决方案:根据业务场景选择合适的事务传播行为,大多数场景下使用默认的REQUIRED即可。

4.2 事务回滚异常的处理技巧

在实际开发中,有时需要根据不同的业务场景灵活控制事务的回滚逻辑,以下是一些事务回滚异常的处理技巧:

4.2.1 手动标记事务回滚

如果需要在不抛出异常的情况下手动触发事务回滚,可以通过TransactionStatus对象的setRollbackOnly()方法实现。例如,在某些业务场景下,虽然没有发生异常,但根据业务规则需要回滚事务:

Plain 复制代码
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.transaction.support.TransactionStatus;

@Service
public class OrderService {

    @Transactional
    public void createOrder(Order order) {
        // 获取当前事务状态
        TransactionStatus status = TransactionSynchronizationManager.getCurrentTransactionStatus();
        
        // 业务逻辑处理
        boolean isSuccess = checkBusinessRule(order);
        if (!isSuccess) {
            // 手动标记事务回滚
            status.setRollbackOnly();
            return;
        }
        
        stockMapper.reduceStock(order.getProductId(), order.getQuantity());
        orderMapper.insertOrder(order);
    }
    
    private boolean checkBusinessRule(Order order) {
        // 业务规则校验
        return order.getAmount() > 0;
    }
}

4.2.2 自定义异常与事务回滚

在实际开发中,通常会定义自定义的业务异常来表示不同的业务错误。为了让自定义异常能够触发事务回滚,需要在@Transactional注解的rollbackFor属性中指定自定义异常类型,或者让自定义异常继承RuntimeException(因为Spring默认对RuntimeException回滚)。

示例代码如下(自定义异常继承RuntimeException):

Plain 复制代码
// 自定义业务异常
public class BusinessException extends RuntimeException {
    public BusinessException(String message) {
        super(message);
    }
}

@Service
public class OrderService {

    @Transactional
    public void createOrder(Order order) {
        if (order.getQuantity() <= 0) {
            // 抛出自定义异常,触发事务回滚
            throw new BusinessException("订单数量不能小于等于0");
        }
        stockMapper.reduceStock(order.getProductId(), order.getQuantity());
        orderMapper.insertOrder(order);
    }
}

4.3 高并发场景下的事务优化

在高并发场景下,事务的使用不当可能会导致数据库性能下降、死锁等问题,以下是一些高并发场景下的事务优化技巧:

4.3.1 缩小事务范围

事务的执行时间越长,占用的数据库资源越多,并发性能越低,同时出现死锁的风险也越高。因此,应尽量缩小事务的范围,只将核心的数据库操作包含在事务中,避免在事务中执行耗时操作(如网络请求、文件IO、复杂计算等)。

优化前的代码(事务中包含耗时操作):

Plain 复制代码
@Transactional
public void createOrder(Order order) {
    // 耗时的网络请求(不应包含在事务中)
    User user = userFeignClient.getUserById(order.getUserId());
    if (user == null) {
        throw new BusinessException("用户不存在");
    }
    // 核心数据库操作
    stockMapper.reduceStock(order.getProductId(), order.getQuantity());
    orderMapper.insertOrder(order);
}

优化后的代码(将耗时操作移出事务):

Plain 复制代码
public void createOrder(Order order) {
    // 耗时的网络请求(移出事务)
    User user = userFeignClient.getUserById(order.getUserId());
    if (user == null) {
        throw new BusinessException("用户不存在");
    }
    // 调用事务方法执行核心数据库操作
    doCreateOrder(order);
}

@Transactional
public void doCreateOrder(Order order) {
    // 核心数据库操作
    stockMapper.reduceStock(order.getProductId(), order.getQuantity());
    orderMapper.insertOrder(order);
}

4.3.2 使用合适的隔离级别

隔离级别越高,数据一致性越好,但并发性能越低。在高并发场景下,应在保证数据一致性的前提下,尽量选择较低的隔离级别。例如,大多数业务场景下使用读已提交(Read Committed)隔离级别即可,既能避免脏读,又能保证较好的并发性能。

配置示例:

Plain 复制代码
@Transactional(isolation = Isolation.READ_COMMITTED)
public void createOrder(Order order) {
    // 业务逻辑
}

4.3.3 避免事务中的锁竞争

在高并发场景下,事务中的锁竞争是导致性能下降的主要原因之一。为了避免锁竞争,可以采取以下措施:

  • 尽量使用行锁,避免使用表锁。例如,在执行更新操作时,尽量通过主键或唯一索引定位数据,避免全表扫描导致的表锁。

  • 控制事务的执行顺序,避免不同事务交叉更新同一批数据导致死锁。例如,多个事务都需要更新A和B两条数据时,都按照先更新A再更新B的顺序执行。

  • 使用乐观锁替代悲观锁。乐观锁通过版本号或时间戳机制实现,不需要在事务中持有锁,适用于读多写少的高并发场景。

乐观锁示例(基于版本号):

Plain 复制代码
// 实体类添加版本号字段
public class Stock {
    private Long id;
    private Long productId;
    private Integer quantity;
    private Integer version; // 版本号字段
    // getter和setter
}

// Mapper接口
public interface StockMapper {
    // 乐观锁更新:只有版本号匹配时才更新
    int reduceStockWithVersion(@Param("productId") Long productId, 
                               @Param("quantity") Integer quantity, 
                               @Param("version") Integer version);
}

// XML映射文件
<update id="reduceStockWithVersion">
    UPDATE stock 
    SET quantity = quantity - #{quantity}, version = version + 1 
    WHERE product_id = #{productId} AND version = #{version}
</update>

// Service方法
@Transactional
public void reduceStock(Long productId, Integer quantity) {
    // 查询库存和版本号
    Stock stock = stockMapper.selectByProductId(productId);
    if (stock == null || stock.getQuantity() < quantity) {
        throw new BusinessException("库存不足");
    }
    // 乐观锁更新
    int rows = stockMapper.reduceStockWithVersion(productId, quantity, stock.getVersion());
    if (rows == 0) {
        // 更新失败,说明库存已被其他事务修改,抛出异常或重试
        throw new BusinessException("库存更新失败,请重试");
    }
}

4.3.4 使用事务超时机制

在高并发场景下,部分事务可能由于资源竞争等原因导致执行时间过长,占用大量数据库资源。通过设置事务超时时间,可以让长时间未执行完成的事务自动回滚,释放数据库资源。

配置示例:

Plain 复制代码
@Transactional(timeout = 10) // 事务超时时间为10秒
public void batchProcessData(List<Data> dataList) {
    // 批量处理数据的业务逻辑
}

第五章 总结与拓展

5.1 文章知识点总结

本文围绕SpringBoot事务展开,从基础理论到实践应用进行了全面讲解,核心知识点总结如下:事务基础层面,明确了事务的定义及ACID四大核心特性,深入分析了脏读、不可重复读等并发问题及对应的数据库隔离级别,为后续SpringBoot事务的学习奠定理论基础;核心机制层面,剖析了Spring事务管理的三大核心接口,详细解读了7种事务传播行为的适用场景,对比了编程式与声明式两种事务实现方式的优劣;使用详解层面,聚焦声明式事务的@Transactional注解,梳理了核心属性的配置方法、注解使用位置及嵌套调用场景;问题解决层面,归纳了事务不生效的常见原因及解决方案,提供了事务回滚异常的处理技巧,并给出高并发场景下的事务优化策略。通过理论与代码示例结合的方式,构建了完整的SpringBoot事务知识体系。

5.2 知识拓展与延伸

除了本文讲解的核心内容,SpringBoot事务还有一些进阶知识点值得关注。例如,分布式事务问题,在微服务架构中,跨服务的事务操作无法依赖单一数据库的事务机制,此时需要引入Seata、Saga等分布式事务解决方案;又如,事务同步机制,Spring提供的TransactionSynchronization接口可以在事务提交或回滚的不同阶段执行自定义逻辑,适用于事务完成后发送消息、更新缓存等场景。此外,SpringBoot 3.x版本中对事务管理的自动配置进行了优化,结合Jakarta EE的规范调整了相关API,开发者在升级框架时需注意适配。

5.3 推荐阅读资料

为帮助大家进一步深化对SpringBoot事务及相关技术的理解,推荐以下优质资料:

  • 官方文档:Spring Framework官方文档的"Transaction Management"章节,是最权威的学习资料,详细阐述了Spring事务的设计理念与实现细节

相关推荐
间彧1 小时前
Docker Compose 数据卷挂载详解与项目实战
后端
合作小小程序员小小店1 小时前
web网页开发,在线%宠物销售%系统,基于Idea,html,css,jQuery,java,ssh,mysql。
java·前端·数据库·mysql·jdk·intellij-idea·宠物
e***74951 小时前
SpringCloud 系列教程:微服务的未来(二)Mybatis-Plus的条件构造器、自定义SQL、Service接口基本用法
spring cloud·微服务·mybatis
合作小小程序员小小店1 小时前
web网页开发,在线%档案管理%系统,基于Idea,html,css,jQuery,java,ssh,mysql。
java·前端·mysql·jdk·html·ssh·intellij-idea
故渊ZY1 小时前
深入解析JVM:核心架构与调优实战
java·jvm·架构
ChinaRainbowSea1 小时前
13. Spring AI 的观测性
java·人工智能·后端·spring·flask·ai编程
-大头.1 小时前
SpringBoot 全面深度解析:从原理到实践,从入门到专家
java·spring boot·后端
Z_Easen1 小时前
Spring AI:Reactor 异步执行中的线程上下文传递实践
java·spring ai
合作小小程序员小小店1 小时前
web网页开发,在线%物流配送管理%系统,基于Idea,html,css,jQuery,java,ssh,mysql。
java·前端·css·数据库·jdk·html·intellij-idea