事务管理
Spring 通过 @Transactional 注解将事务管理从业务代码中剥离,底层依赖 AOP 代理拦截方法调用。
快速使用
@Service
@RequiredArgsConstructor
public class OrderService {
private final OrderRepository orderRepo;
private final InventoryRepository inventoryRepo;
@Transactional // 默认:REQUIRED 传播 + DEFAULT 隔离 + 运行时异常回滚
public Order createOrder(CreateOrderRequest req) {
Order order = orderRepo.save(buildOrder(req));
inventoryRepo.deduct(req.getProductId(), req.getQuantity());
// 任意 RuntimeException → 事务回滚
return order;
}
@Transactional(readOnly = true) // 只读事务:跳过脏检查,部分数据库可路由到从库
public Order getOrder(Long id) {
return orderRepo.findById(id).orElseThrow();
}
}传播行为(Propagation)
控制当前方法与已有事务的关系,默认 REQUIRED。
| 传播行为 | 有事务时 | 无事务时 | 典型场景 |
|---|---|---|---|
REQUIRED(默认) | 加入已有事务 | 新建事务 | 普通业务方法 |
REQUIRES_NEW | 挂起已有,新建独立事务 | 新建事务 | 操作日志、独立提交 |
NESTED | 创建嵌套事务(保存点) | 新建事务 | 部分回滚 |
SUPPORTS | 加入已有事务 | 非事务执行 | 查询(可选事务) |
NOT_SUPPORTED | 挂起已有,非事务执行 | 非事务执行 | 不需要事务的批量操作 |
MANDATORY | 加入已有事务 | 抛出异常 | 必须由调用方开启事务 |
NEVER | 抛出异常 | 非事务执行 | 强制禁止事务 |
REQUIRES_NEW vs NESTED 的区别
REQUIRES_NEW:两个独立事务
外层事务 ────────────────────────────────────► 提交/回滚
内层事务(独立)─────► 提交/回滚(不受外层影响)
NESTED:一个事务 + 保存点
外层事务 ─────────[savepoint]──────────────── 提交/回滚
内层 ─► 回滚到 savepoint(外层继续)
@Transactional
public void processOrder(Order order) {
orderRepo.save(order);
try {
// REQUIRES_NEW:无论 processOrder 是否回滚,操作日志都会保存
auditService.recordLog(order);
} catch (Exception e) {
// 操作日志失败不影响主流程
log.warn("记录日志失败", e);
}
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void recordLog(Order order) {
auditLogRepo.save(new AuditLog(order));
}隔离级别(Isolation)
| 隔离级别 | 脏读 | 不可重复读 | 幻读 | 说明 |
|---|---|---|---|---|
READ_UNCOMMITTED | ✓ | ✓ | ✓ | 最低,几乎不用 |
READ_COMMITTED | ✗ | ✓ | ✓ | PostgreSQL / Oracle 默认 |
REPEATABLE_READ | ✗ | ✗ | ✓ | MySQL InnoDB 默认 |
SERIALIZABLE | ✗ | ✗ | ✗ | 最高,性能最差 |
DEFAULT | — | — | — | 使用数据库默认隔离级别(推荐) |
// 报表统计:防止两次查询结果不一致
@Transactional(isolation = Isolation.REPEATABLE_READ, readOnly = true)
public ReportData generateReport(LocalDate date) { ... }回滚规则
@Transactional 默认只回滚 RuntimeException 及其子类,受检异常(Exception、IOException 等)不回滚:
// 显式声明回滚受检异常
@Transactional(rollbackFor = Exception.class)
public void transfer(Long fromId, Long toId, BigDecimal amount) throws Exception {
accountRepo.debit(fromId, amount);
accountRepo.credit(toId, amount);
}
// 排除某类异常不回滚(如库存告警,不影响主流程)
@Transactional(noRollbackFor = InventoryWarningException.class)
public void createOrder(Order order) throws InventoryWarningException {
orderRepo.save(order);
inventoryService.checkAndWarn(order); // 警告但不回滚
}手动触发回滚:
@Transactional
public void process() {
try {
doSomething();
} catch (BusinessException e) {
// 捕获了异常又想回滚:标记当前事务为回滚
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
log.warn("业务处理失败,事务将回滚: {}", e.getMessage());
}
}事务失效的常见场景
1. 同类内部调用(最常见)
AOP 代理只对通过代理对象的调用生效,同类方法直接互调绕过代理:
@Service
public class OrderService {
@Transactional
public void createOrder(Order order) {
// ❌ 直接调用,不经过代理,sendNotification 的事务不生效
sendNotification(order);
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void sendNotification(Order order) { ... }
}
// 解决方案一:注入自身代理
@Service
public class OrderService {
@Autowired
private OrderService self; // 注入代理对象
@Transactional
public void createOrder(Order order) {
self.sendNotification(order); // ✅ 通过代理调用
}
}
// 解决方案二:拆分到独立 Bean
@Service
public class NotificationService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void send(Order order) { ... }
}2. 方法非 public
Spring AOP 默认只拦截 public 方法,protected / private 方法上的 @Transactional 无效。
3. 异常被吞掉
@Transactional
public void process() {
try {
riskyOperation();
} catch (Exception e) {
// ❌ 异常被捕获且未重新抛出,事务认为方法正常完成,不回滚
log.error("失败", e);
}
}
// 修复:捕获后重新抛出,或调用 setRollbackOnly()4. 多数据源
@Transactional 只管理单个 DataSource,跨数据源操作需要分布式事务。多数据源详见 多数据源。
5. 非 Spring 管理的对象
new OrderService() 创建的对象不经过 Spring 容器,没有代理,事务不生效。
编程式事务
注解方式声明式事务适合大多数场景,编程式事务适合动态控制(如循环中对每条记录单独事务):
@Service
@RequiredArgsConstructor
public class BatchService {
private final TransactionTemplate transactionTemplate;
public void batchProcess(List<Item> items) {
for (Item item : items) {
// 每条记录独立事务,互不影响
transactionTemplate.executeWithoutResult(status -> {
try {
processItem(item);
} catch (BusinessException e) {
log.warn("处理失败,跳过: itemId={}", item.getId());
status.setRollbackOnly(); // 仅回滚当前条目
}
});
}
}
// 需要返回值
public Order createWithResult(CreateOrderRequest req) {
return transactionTemplate.execute(status -> {
return orderRepo.save(buildOrder(req));
});
}
}// TransactionTemplate Bean 注册
@Bean
public TransactionTemplate transactionTemplate(PlatformTransactionManager manager) {
TransactionTemplate template = new TransactionTemplate(manager);
template.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
return template;
}事务与异步
@Async 方法在新线程中运行,不继承父线程的事务上下文:
@Transactional
public void createOrder(Order order) {
orderRepo.save(order);
// ❌ 异步方法在新线程中,没有事务,且 order 可能尚未提交就被读取
asyncService.sendEmailAsync(order);
}
// 正确做法:使用 @TransactionalEventListener,在事务提交后触发事件机制与事务的配合详见 事件机制。
只读事务
@Transactional(readOnly = true)
public List<Order> listOrders(Long userId) { ... }readOnly = true 的效果:
- JPA / Hibernate:跳过脏检查(FlushMode = MANUAL),减少内存和 CPU 消耗
- 支持读写分离的连接池:可路由到从库
- 某些数据库驱动会优化只读连接
分布式事务
跨多个数据库或服务的事务,Spring 内置事务无法支持,常用方案:
| 方案 | 一致性 | 性能 | 实现复杂度 |
|---|---|---|---|
| Seata AT | 强一致 | 中 | 低(注解驱动) |
| Seata TCC | 强一致 | 高 | 高(手写 try/confirm/cancel) |
| 本地消息表 + MQ | 最终一致 | 高 | 中 |
| Saga | 最终一致 | 高 | 中 |
// Seata 全局事务(AT 模式)
@GlobalTransactional // Seata 注解,替代 @Transactional
public void placeOrder(OrderRequest req) {
orderService.create(req); // 服务A,数据库A
inventoryService.deduct(req); // 服务B,数据库B
pointService.award(req); // 服务C,数据库C
}