
文章目录
- 前言
- [第一章 事务基础:你必须掌握的核心概念](#第一章 事务基础:你必须掌握的核心概念)
-
- [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注解实现声明式事务。
使用声明式事务的步骤非常简单:
-
在SpringBoot启动类上添加@EnableTransactionManagement注解(SpringBoot 2.0+版本可省略该注解,因为其会自动识别事务相关依赖并开启事务管理)。
-
在需要事务支持的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的事务注解生效,可以通过以下两种方式解决:
-
将methodB方法提取到另一个Service类中,通过依赖注入的方式调用。
-
在当前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事务的设计理念与实现细节