关于 transactional-patterns
transactional-patterns 是一项指导如何在应用代码中使用 Spring 事务的技能,帮助编码 Agent 保留现有架构,并处理传播、回滚、并发和分布式工作流。
主要功能
- 01选择事务边界
- 02理解事务传播行为
- 03处理回滚与异常
- 04应用乐观锁与重试
- 05用 Saga 协调分布式步骤
- 06管理提交后副作用与验证
适用场景
- 01修改服务前检查事务注解
- 02为受检异常设置回滚规则
- 03在新事务中处理乐观锁冲突
- 04规划提交后事件的持久化投递
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ rrezartprebreza/ spring-boot-skills --skill transactional-patterns安装后在 Claude Code 中输入/transactional-patterns确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
agents/openai.yamlexamples/bad-transactional-service.javagood-transactional-service.javatemplates/TransactionalOrderService.java详细介绍
事务模式
明确边界
- 更改传播行为前,检查项目的 Boot/Framework 版本、事务管理器、持久化技术、 回滚配置和调用方。
- 在应用/服务边界处理多步骤业务事务。保留 Spring Data 仓库事务;仓库注解本身 并非错误。
- 默认传播行为是
REQUIRED——加入现有事务,或创建新事务 - 对适用的只读工作单元使用
readOnly = true。这是一种优化提示, 并不禁止写入、提供授权,也不会自动路由到只读副本。 - 保留现有架构和事务管理器选择。本地数据库事务无法使写入另一个数据库或 HTTP 提供方的操作具备原子性。
以下代码片段仅作示例;领域类和导入项来自应用程序。 编辑服务时,请调整服务模板,并对照 良好示例和不良示例。
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true) // default for all methods in this service
public class OrderService {
@Transactional // overrides readOnly for writes
public Order createOrder(CreateOrderRequest request) {
inventoryService.reserve(request.items()); // participates in same TX
return orderRepository.save(Order.from(request));
}
public Optional<Order> findById(UUID id) {
return orderRepository.findById(id); // readOnly = true inherited
}
}
传播行为
| 传播行为 | 行为 |
|---|---|
REQUIRED(默认) | 加入现有事务,或创建新事务 |
REQUIRES_NEW | 始终创建新事务,并挂起现有事务 |
SUPPORTS | 存在事务时加入;否则无事务地继续 |
NOT_SUPPORTED | 始终在无事务状态下运行 |
MANDATORY | 必须存在现有事务,否则抛出异常 |
NEVER | 必须不存在事务;若存在则抛出异常 |
参与事务的 REQUIRED 调用使用外层事务的隔离级别、超时和只读设置。捕获内部失败
不会清除其仅回滚标记;外层提交仍可能抛出 UnexpectedRollbackException。
外层事务保留资源时,REQUIRES_NEW 需要另一个连接。请考虑连接池容量和锁竞争。
仅将独立提交用于必须在操作失败后仍保留的记录(例如,尝试执行某操作的记录),
而不要用于声称已创建某个后来回滚订单的成功审计记录。不要在独立审计事务中引用
尚未提交的父记录。
// REQUIRES_NEW — for audit logging that must survive rollback
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void logAuditEvent(AuditEvent event) {
auditRepository.save(event); // commits independently of parent TX
}
// Illustrative: record an attempt for an existing order; failures are unchecked.
@Transactional
public void processOrder(Order order) {
auditService.logAuditEvent(new AuditEvent("ORDER_START", order.getId()));
try {
// ... process, may throw
} catch (RuntimeException e) {
auditService.logAuditEvent(new AuditEvent("ORDER_FAILED", order.getId()));
throw e; // parent TX rolls back, audit TX already committed
}
}
自调用陷阱
// ❌ BROKEN — self-invocation bypasses Spring proxy, @Transactional ignored
@Service
public class OrderService {
@Transactional
public void processAll(List<UUID> ids) {
ids.forEach(id -> this.processSingle(id)); // bypasses proxy!
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void processSingle(UUID id) { ... } // never creates new TX
}
// ✅ FIX — extract the independently transactional operation to another bean
@Service
@RequiredArgsConstructor
public class OrderService {
private final OrderProcessor orderProcessor; // separate bean
@Transactional
public void processAll(List<UUID> ids) {
ids.forEach(id -> orderProcessor.processSingle(id)); // goes through proxy
}
}
异常处理
// Default: RuntimeException and Error roll back; checked exceptions do not.
// Check project-wide rollback configuration before adding per-method rules.
@Transactional(rollbackFor = InsufficientInventoryException.class) // checked exception
public Order createOrder(CreateOrderRequest request) throws InsufficientInventoryException {
...
}
绝不要使用 noRollbackFor 来从 OptimisticLockException 中恢复:持久化提供方会将
事务标记为仅回滚状态。应回滚事务,并在全新的事务中重试整个工作单元,同时采用有
界重试策略并重新读取实体。
乐观锁
@Entity
public class Order {
@Version
private Long version; // Hibernate handles conflicts automatically
}
// Handles concurrent updates
@Transactional
public Order updateStatus(UUID id, OrderStatus newStatus) {
Order order = orderRepository.findById(id).orElseThrow();
order.updateStatus(newStatus); // conflict can surface at flush or commit
return orderRepository.save(order);
}
有关依赖和注解导入,请使用针对版本的重试指南。
执行重试的外观层不应预先持有事务,否则每次尝试中的 REQUIRED 方法都会加入该事务。
分布式事务(Saga 模式)
Saga 用于协调持久化的本地步骤;用 @Transactional 包裹远程调用并不能实现 Saga。
- 在一个本地事务中提交待处理订单、Saga 状态和待发送命令。
- 在该事务之外发送命令,并使用稳定的提供方幂等键。
- 在另一个本地事务中,原子地持久化每个已确认的结果和下一条命令。
- 遇到拒绝时,持久化失败状态和补偿命令。独立重试补偿;对于远程结果未知的超时, 进行协调核对。
不要在同一个事务中先保存失败状态,然后再抛出会触发回滚的异常:这会抹掉该状态。 发生崩溃后,恢复流程必须从持久化状态继续。使用项目现有的工作流机制;有关消息 投递和重复效果,请参阅 消息传递/Outbox和 幂等性。
提交后的副作用
对于尽力而为的本地通知,绑定到提交的监听器可防止回滚时发送通知。
对于必须送达的通知,请将发件箱条目与业务变更一同持久化,或使用项目的
持久化发布机制。仅靠 AFTER_COMMIT 无法应对进程崩溃,也无法重试
发送失败的通知。
// Publisher — inside the TX
@Transactional
public Order place(UUID id) {
Order order = orderRepository.findById(id).orElseThrow();
order.place();
eventPublisher.publishEvent(new OrderPlaced(order.getId())); // published now; listener defers handling
return orderRepository.save(order);
}
// Listener — runs ONLY if the TX commits successfully
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void onOrderPlaced(OrderPlaced event) {
emailService.sendConfirmation(event.orderId()); // best effort; no durable retry
}
AFTER_COMMIT 会在数据库提交后运行,但资源可能仍绑定到已完成的
事务。请将数据库写入委托给单独的 REQUIRES_NEW Bean。监听器失败
无法撤销已提交的业务写入;请确保发送失败可被发现并恢复。
默认情况下,没有活动事务时会跳过监听器。@Async 不会带来
持久化送达能力。请参阅领域事件。
验证
通过 Spring 代理进行测试,并在新事务中验证已提交的数据。覆盖受检异常 与非受检异常、内部回滚标记传播、提交时的乐观锁冲突、审计回滚,以及 提交后和回滚后的监听器行为。隔离性和竞争测试应使用生产环境的数据库 引擎。整个测试范围内的回滚可能会掩盖提交后行为。
常见问题
- Agent 移除了仓库的事务注解——保留工作单元和现有 Spring Data 行为。
- Agent 将
AFTER_COMMIT视为可靠消息传递——对于必须送达的通知,请使用发件箱或持久化发布登记表。 - Agent 使用 readOnly 进行副本路由或写入保护——仅凭此标志无法实现这两点。
- Agent 使用 REQUIRES_NEW 提交成功审计条目——应与业务变更一同提交。
- Agent 抑制乐观锁回滚——应在新事务中重试。
- Agent 在
this上调用@Transactional方法——自调用会绕过代理。 - Agent 认为受检异常会触发回滚——必须添加
rollbackFor。 - Agent 在
private方法上使用@Transactional——Spring 代理无法拦截。