Spring 事务管理实战指南
一、概述
事务是数据库操作的基本单元,保证一组操作要么全部成功,要么全部回滚。Spring 提供了多种事务管理方式,从声明式注解到编程式 API,适用于不同复杂度的场景。
本文以一个电商订单支付场景为例,系统介绍 Spring 事务的实现方式、传播行为、隔离级别及常见陷阱。
注:
博客:
https://blog.csdn.net/badao_liumang_qizhi
二、示例场景
java
// 支付成功后:扣减库存 + 更新订单状态 + 记录支付流水
// 三个操作必须在同一事务中,任一失败全部回滚
三、方案一:声明式事务(@Transactional 注解)
3.1 基本用法
java
@Service
public class OrderPaymentService {
@Resource
private OrderRepository orderRepository;
@Resource
private StockRepository stockRepository;
@Resource
private PaymentRecordRepository paymentRecordRepository;
@Transactional(rollbackFor = Exception.class)
public void processPayment(PaymentDto paymentDto) {
// 1. 扣减库存
Stock stock = stockRepository.findBySkuId(paymentDto.getSkuId());
stock.setQty(stock.getQty() - paymentDto.getQty());
stockRepository.save(stock);
// 2. 更新订单状态
Order order = orderRepository.findByOrderNo(paymentDto.getOrderNo());
order.setStatus("PAID");
orderRepository.save(order);
// 3. 记录支付流水
PaymentRecord record = new PaymentRecord();
record.setOrderNo(paymentDto.getOrderNo());
record.setAmount(paymentDto.getAmount());
record.setPayTime(new Date());
paymentRecordRepository.save(record);
}
}
3.2 注解属性详解
| 属性 | 默认值 | 说明 |
|---|---|---|
propagation |
REQUIRED | 事务传播行为 |
isolation |
DEFAULT | 事务隔离级别 |
timeout |
-1(无超时) | 事务超时时间(秒) |
readOnly |
false | 是否只读事务 |
rollbackFor |
RuntimeException | 触发回滚的异常类型 |
noRollbackFor |
无 | 不触发回滚的异常类型 |
3.3 rollbackFor 的重要性
java
// 错误:checked exception 不会触发回滚
@Transactional
public void process() throws IOException {
orderRepository.save(order);
throw new IOException("文件写入失败"); // 事务不会回滚!
}
// 正确:显式指定 rollbackFor
@Transactional(rollbackFor = Exception.class)
public void process() throws IOException {
orderRepository.save(order);
throw new IOException("文件写入失败"); // 事务会回滚
}
规则:
- 默认只对
RuntimeException和Error回滚 checked Exception(如 IOException)默认不回滚- 建议始终写
rollbackFor = Exception.class
3.4 实现原理
Spring 通过 AOP 代理实现声明式事务:
调用方 -> Spring 代理对象 -> 开启事务 -> 执行目标方法 -> 提交/回滚事务
代理方式:
- JDK 动态代理(接口)
- CGLIB 代理(类)
四、事务传播行为(Propagation)
4.1 七种传播行为
| 传播行为 | 说明 | 典型场景 |
|---|---|---|
| REQUIRED | 有事务则加入,无则新建 | 默认值,大多数业务方法 |
| REQUIRES_NEW | 总是新建事务,挂起当前事务 | 日志记录、审计(独立于主事务) |
| NESTED | 在当前事务中创建保存点(嵌套事务) | 部分失败可回滚到保存点 |
| SUPPORTS | 有事务则加入,无则非事务执行 | 查询方法 |
| NOT_SUPPORTED | 总是非事务执行,挂起当前事务 | 不需要事务的操作 |
| MANDATORY | 必须在事务中调用,否则抛异常 | 强制要求调用方有事务 |
| NEVER | 必须在非事务中调用,否则抛异常 | 禁止在事务中执行的操作 |
4.2 REQUIRED vs REQUIRES_NEW vs NESTED
java
@Service
public class OuterService {
@Resource
private InnerService innerService;
@Transactional(rollbackFor = Exception.class)
public void outerMethod() {
// 操作 A
orderRepository.save(order);
// 调用内部方法
innerService.innerMethod();
// 操作 B
stockRepository.save(stock);
}
}
@Service
public class InnerService {
// 场景1:REQUIRED(默认)
// innerMethod 加入 outerMethod 的事务
// innerMethod 异常 -> 整个事务回滚(A、B 都回滚)
@Transactional(propagation = Propagation.REQUIRED)
public void innerMethod() { ... }
// 场景2:REQUIRES_NEW
// innerMethod 在独立事务中执行
// innerMethod 异常 -> 只回滚 innerMethod,outerMethod 可以 catch 后继续
// outerMethod 异常 -> 不影响已提交的 innerMethod
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void innerMethod() { ... }
// 场景3:NESTED
// innerMethod 在 outerMethod 事务的保存点中执行
// innerMethod 异常 -> 回滚到保存点,outerMethod 可以 catch 后继续
// outerMethod 异常 -> innerMethod 也回滚(因为是同一物理事务)
@Transactional(propagation = Propagation.NESTED)
public void innerMethod() { ... }
}
4.3 REQUIRES_NEW 的注意事项
java
// 注意:REQUIRES_NEW 会挂起外层事务,占用额外数据库连接
// 高并发下可能耗尽连接池
// 外层事务持有连接 A(被挂起)
// 内层事务获取连接 B(新事务)
// 如果连接池只有 N 个连接,并发 N 个请求时可能死锁
五、事务隔离级别(Isolation)
5.1 四种隔离级别
| 隔离级别 | 脏读 | 不可重复读 | 幻读 | 说明 |
|---|---|---|---|---|
| READ_UNCOMMITTED | 可能 | 可能 | 可能 | 最低隔离,几乎不用 |
| READ_COMMITTED | 不会 | 可能 | 可能 | Oracle 默认 |
| REPEATABLE_READ | 不会 | 不会 | 可能 | MySQL InnoDB 默认 |
| SERIALIZABLE | 不会 | 不会 | 不会 | 最高隔离,性能最差 |
5.2 使用方式
java
// 使用数据库默认隔离级别(推荐)
@Transactional(isolation = Isolation.DEFAULT)
// 需要读已提交(避免脏读,允许不可重复读)
@Transactional(isolation = Isolation.READ_COMMITTED)
// 需要可重复读(MySQL 默认)
@Transactional(isolation = Isolation.REPEATABLE_READ)
5.3 实际建议
- 大多数场景使用数据库默认隔离级别即可
- MySQL InnoDB 的 REPEATABLE_READ 通过 MVCC 实现,性能开销不大
- 只有在明确需要更低隔离级别(如报表查询)时才手动指定
六、方案二:编程式事务(TransactionTemplate)
6.1 适用场景
- 需要在方法内部精细控制事务边界
- 部分代码需要事务,部分不需要
- 需要根据条件决定是否提交/回滚
6.2 实现方式
java
@Service
public class OrderPaymentService {
@Resource
private TransactionTemplate transactionTemplate;
@Resource
private OrderRepository orderRepository;
@Resource
private PaymentLogRepository paymentLogRepository;
public Boolean processPayment(PaymentDto paymentDto) {
// 事务内:核心业务
Boolean result = transactionTemplate.execute(status -> {
try {
Order order = orderRepository.findByOrderNo(paymentDto.getOrderNo());
order.setStatus("PAID");
orderRepository.save(order);
return true;
} catch (Exception e) {
status.setRollbackOnly(); // 手动标记回滚
return false;
}
});
// 事务外:记录日志(不影响主事务)
try {
PaymentLog log = new PaymentLog();
log.setOrderNo(paymentDto.getOrderNo());
log.setResult(result ? "SUCCESS" : "FAIL");
paymentLogRepository.save(log);
} catch (Exception e) {
// 日志失败不影响返回结果
}
return result;
}
}
6.3 TransactionTemplate 配置
java
@Configuration
public class TransactionConfig {
@Bean
public TransactionTemplate transactionTemplate(PlatformTransactionManager manager) {
TransactionTemplate template = new TransactionTemplate(manager);
template.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
template.setIsolationLevel(TransactionDefinition.ISOLATION_DEFAULT);
template.setTimeout(30); // 30秒超时
return template;
}
}
6.4 与声明式对比
| 维度 | @Transactional | TransactionTemplate |
|---|---|---|
| 控制粒度 | 方法级别 | 代码块级别 |
| 灵活性 | 低 | 高 |
| 代码侵入性 | 低 | 中 |
| 可读性 | 好 | 一般 |
| 适用场景 | 整个方法需要事务 | 方法内部分代码需要事务 |
七、方案三:编程式事务(PlatformTransactionManager)
7.1 最底层的事务控制
java
@Service
public class OrderPaymentService {
@Resource
private PlatformTransactionManager transactionManager;
@Resource
private OrderRepository orderRepository;
public void processPayment(PaymentDto paymentDto) {
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
def.setIsolationLevel(TransactionDefinition.ISOLATION_DEFAULT);
def.setTimeout(30);
TransactionStatus status = transactionManager.getTransaction(def);
try {
Order order = orderRepository.findByOrderNo(paymentDto.getOrderNo());
order.setStatus("PAID");
orderRepository.save(order);
transactionManager.commit(status);
} catch (Exception e) {
transactionManager.rollback(status);
throw e;
}
}
}
7.2 适用场景
- 需要完全手动控制事务生命周期
- 框架集成或底层工具类
- 一般业务代码不推荐使用
八、方案四:XML 声明式事务(AOP 配置)
8.1 实现方式
xml
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="save*" propagation="REQUIRED" rollback-for="Exception"/>
<tx:method name="update*" propagation="REQUIRED" rollback-for="Exception"/>
<tx:method name="delete*" propagation="REQUIRED" rollback-for="Exception"/>
<tx:method name="get*" propagation="SUPPORTS" read-only="true"/>
<tx:method name="find*" propagation="SUPPORTS" read-only="true"/>
<tx:method name="*" propagation="REQUIRED" rollback-for="Exception"/>
</tx:attributes>
</tx:advice>
<aop:config proxy-target-class="true">
<aop:pointcut id="txPointcut"
expression="execution(* com.example.service..*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
</aop:config>
8.2 优缺点
| 优点 | 缺点 |
|---|---|
| 统一管理,不侵入代码 | 配置与代码分离,不直观 |
| 按方法名模式批量配置 | 灵活性差,无法针对单个方法定制 |
| 适合遗留项目统一加事务 | 新项目不推荐 |
8.3 适用场景
- 遗留项目需要统一添加事务管理
- 团队规范要求统一事务配置
- 与注解方式可以共存(注解优先级更高)
九、@Transactional 失效的常见陷阱
9.1 自调用问题
java
@Service
public class OrderService {
// 事务不生效!因为是 this 调用,不经过代理
public void methodA() {
this.methodB(); // 直接调用,不走 AOP 代理
}
@Transactional(rollbackFor = Exception.class)
public void methodB() {
// 期望有事务,但实际没有
}
}
解决方案:
java
// 方案1:注入自身(推荐)
@Service
public class OrderService {
@Resource
private OrderService self; // 注入代理对象
public void methodA() {
self.methodB(); // 通过代理调用,事务生效
}
@Transactional(rollbackFor = Exception.class)
public void methodB() { ... }
}
// 方案2:拆分到不同 Service
@Service
public class OrderService {
@Resource
private OrderTransactionService orderTransactionService;
public void methodA() {
orderTransactionService.methodB(); // 跨 Bean 调用,事务生效
}
}
// 方案3:从 ApplicationContext 获取代理
@Service
public class OrderService implements ApplicationContextAware {
private ApplicationContext context;
public void methodA() {
OrderService proxy = context.getBean(OrderService.class);
proxy.methodB();
}
@Transactional(rollbackFor = Exception.class)
public void methodB() { ... }
}
9.2 异常被吞
java
@Transactional(rollbackFor = Exception.class)
public void process() {
try {
orderRepository.save(order);
int x = 1 / 0; // 异常
} catch (Exception e) {
log.error("处理失败", e);
// 异常被 catch 了,Spring 感知不到,事务不会回滚!
}
}
解决方案:
java
// 方案1:catch 后重新抛出
catch (Exception e) {
log.error("处理失败", e);
throw e; // 重新抛出,事务回滚
}
// 方案2:手动标记回滚
catch (Exception e) {
log.error("处理失败", e);
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
9.3 方法访问修饰符
java
// 事务不生效!@Transactional 只对 public 方法有效(基于代理)
@Transactional(rollbackFor = Exception.class)
private void process() { ... } // private 方法,代理无法拦截
@Transactional(rollbackFor = Exception.class)
protected void process() { ... } // protected,CGLIB 代理可能生效,但不推荐
规则 :@Transactional 只应用于 public 方法。
9.4 非 Spring 管理的类
java
// 事务不生效!类没有被 Spring 管理
public class OrderService { // 缺少 @Service 或 @Component
@Transactional(rollbackFor = Exception.class)
public void process() { ... }
}
9.5 多数据源未指定事务管理器
java
// 如果有多个数据源,需要指定使用哪个事务管理器
@Transactional(value = "secondaryTransactionManager", rollbackFor = Exception.class)
public void process() { ... }
十、只读事务的优化
java
// 查询方法标记为只读事务
@Transactional(readOnly = true)
public List<Order> findOrders(String userId) {
return orderRepository.findByUserId(userId);
}
好处:
- MySQL 对只读事务有优化(不记录 undo log)
- Spring 可以将只读提示传递给 JDBC 驱动
- 在主从架构中,只读事务可以路由到从库
十一、事务超时
java
// 设置事务超时为 10 秒
@Transactional(timeout = 10, rollbackFor = Exception.class)
public void processLargeData() {
// 如果 10 秒内未完成,事务自动回滚
}
注意:超时计时从事务开始到最后一条 SQL 执行前。如果最后一条 SQL 本身执行很久,超时可能不会立即生效。
十二、方案对比总结
| 维度 | @Transactional | TransactionTemplate | PlatformTransactionManager | XML 配置 |
|---|---|---|---|---|
| 控制粒度 | 方法级 | 代码块级 | 代码块级 | 方法名模式 |
| 代码侵入 | 低 | 中 | 高 | 无 |
| 灵活性 | 中 | 高 | 最高 | 低 |
| 可读性 | 好 | 一般 | 差 | 一般 |
| 学习成本 | 低 | 中 | 高 | 中 |
| 推荐程度 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐ | ⭐⭐ |
十三、最佳实践清单
- 始终写
rollbackFor = Exception.class,避免 checked exception 不回滚 - 避免自调用:同类中方法互调不走代理,事务不生效
- 事务方法必须是 public
- 不要在事务方法中 catch 异常后不处理:要么重新抛出,要么手动标记回滚
- 事务范围尽量小:只包含必须原子性的操作,避免长事务
- 查询方法用
readOnly = true:提升性能,支持读写分离 - 设置合理的 timeout:防止长事务占用连接
- REQUIRES_NEW 谨慎使用:会占用额外连接,高并发下可能耗尽连接池
- 日志/通知等非核心操作放在事务外:避免影响主事务
- 多数据源场景显式指定事务管理器