欢迎光临
我们一直在努力

事务管理深度解析:@Transactional 的正确使用姿势(Spring Boot 4 实战)

事务是后端开发中最容易"以为对了实际没生效"的环节。本文将系统拆解 Spring 事务抽象、传播行为、隔离级别、失效场景、声明式与编程式事务,以及多数据源下的事务方案,帮你彻底避开生产事故。


一、Spring 事务抽象:PlatformTransactionManager

1️⃣ 核心接口

Spring 事务的核心抽象是 PlatformTransactionManager:

PlatformTransactionManager
├── DataSourceTransactionManager (JDBC / MyBatis / MP)
├── JpaTransactionManager (JPA / Hibernate)
├── JtaTransactionManager (分布式事务)
└── ReactiveTransactionManager (响应式)

📌 Spring Boot 4 自动配置:只要 classpath 中有 DataSource + spring-tx,就会自动创建 DataSourceTransactionManager。


2️⃣ 事务的 ACID

特性

说明

Atomicity(原子性)

全部成功或全部回滚

Consistency(一致性)

数据从一个一致状态到另一个一致状态

Isolation(隔离性)

并发事务互不干扰

Durability(持久性)

提交后永久生效


二、@Transactional 注解全解

1️⃣ 基础用法

@Service
public class OrderService {

@Autowired
private OrderMapper orderMapper;

@Autowired
private AccountMapper accountMapper;

@Transactional
public void createOrder(Long userId, BigDecimal amount) {
// 扣余额
accountMapper.deduct(userId, amount);
// 创建订单
orderMapper.insert(new Order(userId, amount));
// 如果这里抛异常,上面扣的钱会回滚
}
}

✅ 默认行为:

  • 传播行为:Propagation.REQUIRED
  • 隔离级别:Isolation.DEFAULT(跟随数据库,MySQL 默认 REPEATABLE_READ)
  • 回滚:RuntimeException 和 Error 自动回滚
  • 超时:-1(不超时)
  • 只读:false

2️⃣ 完整参数

@Transactional(
propagation = Propagation.REQUIRED, // 传播行为
isolation = Isolation.READ_COMMITTED, // 隔离级别
rollbackFor = {Exception.class}, // 回滚异常类型
noRollbackFor = {BusinessException.class}, // 不回滚的异常
timeout = 30, // 超时(秒)
readOnly = false // 只读优化
)
public void complexBusiness() {
// …
}


三、传播行为 7 种全解(面试必问)

1️⃣ 速查表

传播行为

说明

场景

REQUIRED(默认)

有事务加入,没有新建

大多数业务方法

REQUIRES_NEW

挂起当前事务,新建独立事务

日志/审计(必须独立提交)

NESTED

嵌套事务(保存点)

部分回滚

SUPPORTS

有事务加入,没有非事务运行

查询方法

NOT_SUPPORTED

非事务运行,挂起当前事务

批量处理

MANDATORY

必须在事务中,否则抛异常

强制调用方提供事务

NEVER

必须在非事务中,否则抛异常

极少用


2️⃣ 经典场景:REQUIRES_NEW

@Service
public class BusinessService {

@Autowired
private LogService logService;

@Transactional
public void placeOrder(Order order) {
// 主事务
orderMapper.insert(order);

try {
// 独立事务:即使主事务回滚,日志也提交
logService.saveLog("订单创建: " + order.getId());
} catch (Exception e) {
// 日志失败不影响主流程
}

if (order.getAmount().compareTo(BigDecimal.ZERO) < 0) {
throw new IllegalArgumentException("金额不能为负");
}
}
}

@Service
public class LogService {

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveLog(String content) {
logMapper.insert(new Log(content));
}
}

📌 执行流程:

placeOrder 事务开始
→ orderMapper.insert()
→ saveLog 事务开始(挂起 placeOrder 事务)
→ logMapper.insert() → 提交
→ placeOrder 事务恢复
→ 抛异常 → placeOrder 回滚
→ 结果:订单没插入,日志已提交 ✅


3️⃣ NESTED vs REQUIRES_NEW

对比

NESTED

REQUIRES_NEW

是否独立事务

否(同一连接,保存点)

是(新连接)

外层回滚

内层一起回滚

内层不受影响

内层回滚

外层可捕获,不回滚外层

外层可捕获,不回滚外层

数据库要求

需要 JDBC 保存点支持


四、隔离级别与并发问题

1️⃣ 并发事务的 3 大问题

问题

说明

示例

脏读​

读到未提交的数据

A 改了没提交,B 读到了

不可重复读​

同一事务内两次读结果不同

A 事务中两次查余额,B 事务中间改了

幻读​

同一事务内两次查询结果集行数不同

A 事务查 count,B 事务插入新行


2️⃣ 隔离级别对照

隔离级别

脏读

不可重复读

幻读

性能

READ_UNCOMMITTED

最高

READ_COMMITTED

REPEATABLE_READ

SERIALIZABLE

最低

📌 MySQL 默认 REPEATABLE_READ,Oracle 默认 READ_COMMITTED


3️⃣ 实战配置

@Transactional(isolation = Isolation.READ_COMMITTED)
public BigDecimal getBalance(Long userId) {
return accountMapper.getBalance(userId);
}


五、@Transactional 失效的 6 种经典场景

❌ 场景1:同类方法自调用

@Service
public class UserService {

public void methodA() {
methodB(); // ❌ 事务失效!
}

@Transactional
public void methodB() {
// …
}
}

📌 原因:Spring 事务基于代理,自调用不经过代理对象

✅ 解决方式 1:注入自己

@Service
public class UserService {

@Autowired
private UserService self; // 注入代理对象

public void methodA() {
self.methodB(); // ✅ 走代理
}

@Transactional
public void methodB() {
// …
}
}

✅ 解决方式 2:AOP 上下文

AopContext.currentProxy(); // 需要 @EnableAspectJAutoProxy(exposeProxy = true)

✅ 解决方式 3:拆到另一个 Service


❌ 场景2:方法不是 public

@Transactional
private void method() {} // ❌ 不生效

✅ 解决:改为 public


❌ 场景3:异常被 catch 吞掉

@Transactional
public void method() {
try {
int i = 1 / 0;
} catch (Exception e) {
// ❌ 吞掉异常,事务不回滚
}
}

✅ 解决:

catch (Exception e) {
throw e; // 或 throw new RuntimeException(e);
}


❌ 场景4:异常类型不匹配

@Transactional
public void method() throws Exception {
throw new Exception("checked exception"); // ❌ 默认不回滚
}

✅ 解决:

@Transactional(rollbackFor = Exception.class)


❌ 场景5:数据库引擎不支持事务

— MyISAM 不支持事务
CREATE TABLE user (…) ENGINE=MyISAM; — ❌

✅ 解决:用 InnoDB


❌ 场景6:多数据源未指定事务管理器

@Transactional // ❌ 不知道用哪个事务管理器

✅ 解决:

@Transactional(transactionManager = "masterTransactionManager")


六、声明式 vs 编程式事务

1️⃣ 声明式(@Transactional)

✅ 优点:简单、侵入性低

❌ 缺点:粒度粗、自调用失效


2️⃣ 编程式(TransactionTemplate)

@Service
public class UserService {

@Autowired
private TransactionTemplate transactionTemplate;

public Result<?> batchCreate(List<User> users) {
return transactionTemplate.execute(status -> {
try {
users.forEach(userMapper::insert);
return Result.success();
} catch (Exception e) {
status.setRollbackOnly(); // 手动回滚
return Result.error("批量创建失败");
}
});
}
}

✅ 适用场景:需要细粒度控制、try-catch 内回滚


七、多数据源事务方案

方案1:指定事务管理器(单事务)

@Transactional(transactionManager = "masterTransactionManager")
public void masterOp() {}

@Transactional(transactionManager = "slaveTransactionManager")
public void slaveOp() {}

📌 限制:两个操作不能在同一事务中


方案2:JTA(分布式事务)

引入 Atomikos / Narayana:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jta-atomikos</artifactId>
</dependency>

📌 XA 两阶段提交,性能差,不推荐高并发


方案3:Seata(阿里开源,推荐)

<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>

📌 AT 模式,无侵入,性能优于 XA


八、Spring Boot 4 中的变化

  • 虚拟线程下事务同步管理器兼容
  • 事务超时与虚拟线程调度更精确
  • 对 record 类型事务代理支持完善

九、常见坑位总结

解决

自调用事务失效

拆类 / 注入代理 / AopContext

异常被吞

重新抛出

checked 异常不回滚

rollbackFor = Exception.class

多数据源事务混乱

明确指定 transactionManager

只读事务没优化

@Transactional(readOnly = true)

长事务锁表

缩小事务范围,不要包含 RPC 调用


十、本篇总结(5 条核心)

  • @Transactional 默认只对 RuntimeException 回滚
  • 同类自调用事务失效是最高频坑
  • REQUIRES_NEW 用于独立提交(如日志)
  • 生产环境隔离级别用 READ_COMMITTED + 乐观锁
  • 多数据源事务用 Seata,别用 XA
  • 赞(0)
    未经允许不得转载:171主机测评 » 事务管理深度解析:@Transactional 的正确使用姿势(Spring Boot 4 实战)
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址