在研究Mall项目的订单代码时,我发现一个有趣的现象:一个方法内操作了6张表,包含14步业务逻辑,这些操作全部放在一个事务里,居然没有出现问题。
经过深入研究,我发现了6种比单纯使用 @Transactional 注解更灵活的事务控制方法,并创建了一个demo项目进行验证。
我们要解决的痛点
在日常开发中,@Transactional 注解无法妥善处理以下几个典型场景:
- 库存不足时:希望保留订单记录并标记为“待补货”,但不知道如何让事务不回滚。
- 发MQ消息:在事务内发送消息,事务回滚后消息却已经发出,导致数据不一致。
- 批量操作:处理100个订单发货,其中一个失败就导致全部回滚,但我们希望成功的订单能继续处理。
- 记录日志:即使业务失败,也希望记录下操作日志,但事务回滚会导致日志一同被清除。
- 隔离级别与超时:不清楚
@Transactional 的各类参数应如何配置与使用。
本文将用实际代码演示这6种高级事务控制方案的实现。
目录
- 编程式事务:精确区分业务失败与系统异常
- @Transactional参数详解:隔离级别与超时控制
- 事务同步器:确保事务提交后才发送MQ
- 事务事件监听:解耦业务主逻辑与副作用操作
- 手动控制事务:实现灵活的批量操作
- 事务传播机制:3种最常用场景解析
关于demo项目
本文所有示例代码均来自项目:https://gitee.com/sh_wangwanbao/simple-transactional
项目特点:集成了测试框架,可通过反射自动构建测试参数,启动即可运行测试并自动生成Markdown报告。无需手动准备测试数据,也无需逐个执行测试用例。
使用方法:导入数据库脚本(doc/simple-transactional-init.sql),修改配置文件后启动项目,即可查看完整的测试结果。
编程式事务:区分业务失败和系统异常
这是我在Mall项目中遇到的一个实际场景:订单创建后需要调用风控服务进行检查。
- 风控不通过(业务规则):订单需要保留,标记为“待审核”,等待人工复核。
- 风控服务挂掉(系统故障):订单必须回滚,不能留下脏数据。
仅使用 @Transactional 注解无法做到这一点。因为它只能通过抛出异常来触发回滚,无法区分这两种截然不同的情况。
TransactionTemplate 实现动态控制
public OrderResult createOrder(OrderParam param) {
return transactionTemplate.execute(status -> {
try {
// 1. 创建订单
Order order = buildOrder(param);
orderMapper.insert(order);
// 2. 创建订单商品明细
List<OrderItem> items = buildOrderItems(order);
orderItemMapper.batchInsert(items);
// 3. 锁定库存
lockStock(param.getItems());
// 4. 调用风控服务检查
RiskCheckResult riskResult = riskService.check(order);
if (!riskResult.isPass()) {
// 风控不通过 - 业务失败,但不触发回滚
order.setStatus(OrderStatus.WAIT_AUDIT); // 标记为待审核
order.setNote("风控检查未通过:" + riskResult.getReason());
orderMapper.updateById(order);
// 关键:不调用 status.setRollbackOnly()
// 订单和商品明细都会在数据库中保留
return OrderResult.fail("订单需人工审核");
}
// 风控通过,订单创建成功
return OrderResult.success(order.getId());
} catch (RiskServiceException e) {
// 风控服务异常 - 系统故障,必须回滚
log.error("风控服务异常", e);
status.setRollbackOnly();
return OrderResult.error("系统异常,请稍后重试");
} catch (Exception e) {
// 其他异常也触发回滚
status.setRollbackOnly();
return OrderResult.error(e.getMessage());
}
});
}
这才是编程式事务的价值
| 场景 |
@Transactional |
TransactionTemplate |
| 风控不通过 |
抛异常 → 全部回滚 |
不回滚,保留订单记录 |
| 风控服务挂掉 |
抛异常 → 全部回滚 |
回滚,不留脏数据 |
| 库存不足 |
抛异常 → 全部回滚 |
保留订单,标记为“待补货” |
核心区别:能够精确区分“业务规则失败”和“系统异常故障”,并动态决定是否回滚。
我进行的测试结果如下:
# 测试风控不通过(高金额订单)
POST /programmatic/risk-check
# 结果
订单ID:8
订单状态:待审核
订单备注:风控检查未通过:金额过高
数据库:订单和商品明细都成功保留
编程式事务的这种精细控制能力,在传统的声明式事务中是无法实现的。
@Transactional的参数,我被坑过
在Mall项目中,商品创建方法是这么写的:
@Transactional(
isolation = Isolation.REPEATABLE_READ, // 隔离级别
propagation = Propagation.REQUIRED, // 传播行为
timeout = 30, // 超时时间
rollbackFor = Exception.class // 回滚异常类型
)
public int createProduct(ProductParam param) {
// 插入8张表...
}
我之前也习惯直接使用 @Transactional 而不加任何参数,直到踩了几次坑后才明白这些参数的重要性。
isolation 参数需要特别注意
有一次将数据库从MySQL迁移到PostgreSQL后,突然出现了幻读问题。原因在于:
- MySQL的默认隔离级别是
REPEATABLE_READ(可重复读)。
- PostgreSQL的默认隔离级别是
READ_COMMITTED(读已提交)。
如果代码中没有显式指定隔离级别,更换数据库环境就可能导致程序行为出现差异,引发难以排查的问题。
因此建议:
// 明确指定隔离级别,不依赖数据库的默认值
@Transactional(isolation = Isolation.REPEATABLE_READ)
public void someMethod() {
// 避免因环境切换导致的事务行为变化
}
timeout 和 rollbackFor 参数简述
timeout:防止长事务锁表,影响系统性能。
@Transactional(timeout = 30) // 设置30秒超时
public void complexTask() {
// ... 复杂业务逻辑
}
rollbackFor:Spring默认只在抛出 RuntimeException 及其子类时才回滚事务,对于 Checked Exception 则不会回滚。为了明确行为,建议显式指定。
@Transactional(rollbackFor = Exception.class) // 明确指定所有异常都回滚
public void createOrder() throws Exception {
// ... 业务逻辑
}
为 @Transactional 注解合理地配置这些参数,能够有效避免许多潜在的线上问题。
事务提交后发MQ,我之前都做错了
订单创建成功后,通常需要发送一条MQ消息(例如,用于30分钟后自动取消未支付的订单)。我之前是这么写的:
@Transactional
public void createOrder() {
orderMapper.insert(order);
// 直接发送MQ消息
mqSender.send("order.cancel.delay", order.getId());
}
这段代码看起来没问题,但实际上存在一个致命缺陷。
问题出在执行时机上
问题本质:MQ消息已经成功发送出去了,但此时数据库事务可能因为后续操作失败而回滚,导致订单根本不存在。30分钟后,消息消费者尝试取消订单时,会发现订单不存在,产生数据不一致。
这就是 “副作用的时机与事务一致性”问题:
- 订单插入、库存扣减 → 在同一个数据库事务内,遵循ACID原则,要么全部成功,要么全部回滚。
- MQ消息发送 → 不在数据库事务管理范围内,一旦发出就无法撤回。
使用事务同步器解决此问题
Spring提供了事务生命周期的钩子(TransactionSynchronization),允许我们在事务的特定阶段执行回调。
@Transactional
public void createOrder() {
orderMapper.insert(order);
// 注册事务同步器
TransactionSynchronizationManager.registerSynchronization(
new TransactionSynchronization() {
@Override
public void afterCommit() {
// 只有事务提交成功后,这里才会执行
mqSender.send("order.cancel.delay", order.getId());
log.info("MQ消息已发送");
}
@Override
public void afterCompletion(int status) {
if (status == STATUS_ROLLED_BACK) {
log.info("事务已回滚,MQ消息不会被发送");
}
}
}
);
}
核心区别:只有订单数据真正持久化到数据库(事务提交)后,才会发送MQ消息。如果事务回滚,消息则不会发送。
事务同步器的4个生命周期钩子
TransactionSynchronizationManager.registerSynchronization(
new TransactionSynchronization() {
@Override
public void beforeCommit(boolean readOnly) {
log.info("【阶段1-beforeCommit】事务即将提交");
// 可以在这里进行最后的数据校验
}
@Override
public void beforeCompletion() {
log.info("【阶段2-beforeCompletion】事务即将完成");
// 可以在这里清理临时资源
}
@Override
public void afterCommit() {
log.info("【阶段3-afterCommit】事务已提交");
// 数据已持久化,可以安全地发MQ、清理缓存
}
@Override
public void afterCompletion(int status) {
String statusStr = (status == STATUS_COMMITTED) ? "提交" : "回滚";
log.info("【阶段4-afterCompletion】事务已完成,状态:{}", statusStr);
}
}
);
这四个方法的执行顺序是固定的:beforeCommit → beforeCompletion → afterCommit → afterCompletion。
哪些场景必须使用 afterCommit
所有“对外的、具有副作用”的操作,都应该考虑放在 afterCommit 中执行:
场景1:发送MQ消息
@Override
public void afterCommit() {
// 发送延迟取消订单的消息
mqSender.send("order.cancel.delay", orderId);
}
场景2:清理Redis缓存
@Override
public void afterCommit() {
// 清理商品详情缓存
redisTemplate.delete("product:" + productId);
}
场景3:记录业务日志到另一个数据库
@Override
public void afterCommit() {
// 写入独立的日志库(不在当前主事务内)
logMapper.insert(businessLog);
}
场景4:调用外部服务(如短信、推送)
@Override
public void afterCommit() {
// 通知第三方系统订单创建成功
thirdPartyService.notify(order);
}
核心原则:确保只有业务数据真正持久化后,才让外部系统感知到状态变化。
同库的日志记录也需要用 afterCommit 吗?
理论上,如果日志表和订单表在同一个数据库的同一个事务内,即使提前写入,事务回滚时日志也会一起回滚,不会造成数据不一致。
但在实际业务中,我们通常希望:
- 解耦:将订单核心业务与日志记录逻辑分离。
- 性能:避免日志操作增加主事务的耗时。
- 重试:日志记录失败可以独立重试,不影响主订单业务。
因此,即使同库,也建议将日志记录放在 afterCommit 中异步执行。
我进行的测试输出如下,验证了执行顺序的可靠性:
# 运行测试
POST /synchronization/phases
# 控制台输出
【阶段1-beforeCommit】事务即将提交
【阶段2-beforeCompletion】事务即将完成
【阶段3-afterCommit】事务已提交
【阶段4-afterCompletion】事务已完成,状态:提交
MQ消息已发送
事务事件监听:解耦副作用操作
订单创建成功后,往往需要完成多个后续操作:发送MQ、记录日志、发送通知等。如果都写在主方法里,代码会变得臃肿:
@Transactional
public void createOrder() {
orderMapper.insert(order);
// 业务逻辑越来越多,耦合严重
mqSender.send(...);
logService.save(...);
notifyService.send(...);
}
而且事务范围会被不必要地扩大,难道发短信、发推送也需要在数据库事务里吗?
使用事务事件监听进行解耦
第1步:定义领域事件
@Getter
@AllArgsConstructor
public class OrderCreatedEvent {
private String orderSn;
private Long memberId;
private BigDecimal amount;
}
第2步:在业务方法中发布事件
@Service
public class OrderService {
@Autowired
private ApplicationEventPublisher eventPublisher;
@Transactional
public void createOrder(OrderParam param) {
// 创建订单
orderMapper.insert(order);
// 发布事件(事件会被“挂起”,监听器何时处理取决于其配置)
OrderCreatedEvent event = new OrderCreatedEvent(
order.getOrderSn(),
order.getMemberId(),
order.getTotalAmount()
);
eventPublisher.publishEvent(event);
log.info("订单创建事件已发布");
}
}
第3步:编写事件监听器
@Component
public class OrderEventListener {
// 关键:使用 @TransactionalEventListener 并指定 phase = AFTER_COMMIT
// 事件会被暂存,直到事务提交成功后才被处理
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void handleOrderCreated(OrderCreatedEvent event) {
log.info("监听到订单创建事件:{}", event.getOrderSn());
// 这些操作将在事务提交后才执行
mqSender.send("order.cancel", event.getOrderSn());
logMapper.insert(log);
notifyService.send(event.getMemberId());
}
// 可以监听事务回滚后的事件
@TransactionalEventListener(phase = TransactionPhase.AFTER_ROLLBACK)
public void handleOrderFailed(OrderCreatedEvent event) {
log.info("订单创建失败(事务回滚):{}", event.getOrderSn());
}
}
事件发布与事务的关系
这里容易产生混淆:publishEvent 方法本身与事务无关,但监听器的执行时机取决于其监听方式。
关键区别:
| 监听方式 |
执行时机 |
事务回滚的影响 |
@EventListener |
立即执行 |
已执行的副作用操作无法撤销 |
@TransactionalEventListener(AFTER_COMMIT) |
事务提交后 |
事务回滚则监听器不执行 |
@TransactionalEventListener(AFTER_ROLLBACK) |
事务回滚后 |
只有事务回滚时才执行 |
事务事件监听的4个阶段
// 提交前(可进行最后校验)
@TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT)
public void beforeCommit(OrderCreatedEvent event) {
// 事务即将提交,可以进行最后的业务校验
}
// 提交后(执行副作用操作)
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void afterCommit(OrderCreatedEvent event) {
// 数据已持久化,可以安全地发MQ、清缓存
}
// 回滚后(记录失败日志)
@TransactionalEventListener(phase = TransactionPhase.AFTER_ROLLBACK)
public void afterRollback(OrderCreatedEvent event) {
// 事务失败,记录失败日志或执行补偿操作
}
// 完成后(清理资源,无论成功失败)
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMPLETION)
public void afterCompletion(OrderCreatedEvent event) {
// 无论事务提交还是回滚都会执行,适合做资源清理
}
几个需要注意的地方
1. 必须在事务方法内发布事件
// 错误:方法没有 @Transactional
public void createOrder() {
orderMapper.insert(order);
eventPublisher.publishEvent(event); // AFTER_COMMIT 监听器不会触发!
}
// 正确:方法有 @Transactional
@Transactional
public void createOrder() {
orderMapper.insert(order);
eventPublisher.publishEvent(event); // 监听器会在事务提交后触发
}
2. 子事务的事件跟随子事务的边界
@Transactional
public void parentMethod() {
// 父事务逻辑
childMethod(); // 子事务(REQUIRES_NEW)
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void childMethod() {
orderMapper.insert(order);
eventPublisher.publishEvent(event); // 监听器跟随子事务的提交时机
}
3. 无事务环境下的后备执行
// 默认情况下,如果没有活动事务,AFTER_COMMIT监听器不会执行
// 可以通过 fallbackExecution = true 来改变此行为
@TransactionalEventListener(
phase = TransactionPhase.AFTER_COMMIT,
fallbackExecution = true // 即使没有事务也会执行
)
public void handleEvent(OrderCreatedEvent event) {
// ...
}
与事务同步器的对比
| 方式 |
代码耦合度 |
扩展性 |
适用场景 |
TransactionSynchronization |
高(在业务方法内部注册) |
低 |
简单场景,只有1-2个后续操作 |
@TransactionalEventListener |
低(基于发布-订阅模型) |
高 |
复杂场景,有多个需要解耦的后续操作 |
建议:
- 如果只有1-2个简单的后续操作,可以直接使用
TransactionSynchronization。
- 如果后续操作较多,或者未来可能扩展,强烈推荐使用
@TransactionalEventListener 进行解耦。
这样做的好处是业务逻辑清晰,当需要增加新的后续操作(比如新增一个积分发放)时,只需编写一个新的监听器即可,无需修改原有的订单创建方法。
批量操作必须用手动事务
假设需要批量发货100个订单,如果其中第50个订单发货失败,该如何处理?
如果使用 @Transactional 注解:
@Transactional
public void batchDelivery(List<Long> orderIds) {
for (Long orderId : orderIds) {
// 发货逻辑
}
}
问题在于:100个订单的处理被包裹在同一个事务中,任何一个失败都会导致整个事务回滚,前面已经成功的49个订单也会被撤销。
但实际的业务需求往往是:成功的订单正常发货,失败的订单单独记录下来,进行后续处理。
@Service
public class OrderBatchService {
@Autowired
private PlatformTransactionManager transactionManager;
public BatchResult batchDelivery(List<Long> orderIds) {
List<Long> success = new ArrayList<>();
List<String> failed = new ArrayList<>();
for (Long orderId : orderIds) {
// 为每个订单创建独立的事务定义
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
TransactionStatus status = transactionManager.getTransaction(def);
try {
// 单个订单的发货逻辑
Order order = orderMapper.selectById(orderId);
order.setStatus(2); // 已发货
orderMapper.updateById(order);
reduceStock(order);
// 手动提交当前订单的事务
transactionManager.commit(status);
success.add(orderId);
} catch (Exception e) {
// 手动回滚当前订单的事务
transactionManager.rollback(status);
failed.add("订单" + orderId + ":" + e.getMessage());
}
}
return new BatchResult(success, failed);
}
}
高级用法:为批处理定制事务属性
对于定时任务、后台批处理等高并发或长时间运行的场景,可以显式控制每个事务的属性,以优化性能。
public BatchResult batchCloseOrder(List<Long> orderIds) {
for (Long orderId : orderIds) {
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
// 强制开启新事务(即使外层存在事务也独立)
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
// 降低隔离级别为读已提交,减少锁竞争
def.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
// 设置超时时间,避免单个失败任务长时间锁住资源
def.setTimeout(10);
TransactionStatus status = transactionManager.getTransaction(def);
try {
Order order = orderMapper.selectById(orderId);
order.setStatus(4); // 已关闭
orderMapper.updateById(order);
transactionManager.commit(status);
} catch (Exception e) {
transactionManager.rollback(status);
}
}
}
三种批量处理方式对比
| 方式 |
事务范围 |
单条失败的影响 |
适用场景 |
@Transactional |
整个批次一个事务 |
全部回滚 |
不适合需要部分成功的批量操作 |
| 手动事务(默认属性) |
每条数据独立事务 |
只回滚当前条 |
普通的批处理任务 |
| 手动事务(定制属性) |
每条数据独立事务 |
只回滚当前条 |
高并发、性能要求高的批处理 |
在实际测试中,处理100个订单,97个成功,3个失败。结果是97个成功的订单状态正常更新,3个失败的订单信息被记录下来。
核心价值:实现了每条数据的独立事务控制,部分失败不会影响其他成功的数据,这对于后端开发中的健壮性要求至关重要。
事务传播机制:3种常用场景
创建订单时,通常会调用另一个方法createOrderItems来插入订单商品。如果两个方法都标注了 @Transactional,会发生什么?
Spring定义了7种事务传播行为,但最常用的只有3种:REQUIRED、REQUIRES_NEW 和 NESTED。
REQUIRED(默认):同成同败
行为:如果当前存在事务,则加入该事务;如果当前没有事务,则新建一个事务。这是最常用的设置。
// 父方法
@Transactional(propagation = Propagation.REQUIRED)
public void createOrder() {
orderMapper.insert(order);
createOrderItems(order.getId()); // 子方法加入当前事务
}
// 子方法
@Transactional(propagation = Propagation.REQUIRED)
public void createOrderItems(Long orderId) {
itemMapper.batchInsert(items);
}
关键点:
- 父子方法在同一个物理事务中执行。
- 子方法抛出异常 → 整个事务回滚(父方法的操作也一并回滚)。
- 订单和订单商品真正做到“同生共死”。
适用场景:同一个业务流程内的多个步骤需要保证原子性,“要么全做,要么全不做”。80%的业务场景都使用这个传播行为。
REQUIRES_NEW:独立事务
行为:挂起当前事务(如果存在),创建一个全新的事务。新事务独立提交或回滚。
// 父方法
@Transactional
public void createOrder() {
orderMapper.insert(order);
logService.saveLog(log); // 新事务,独立提交
// 后面的代码可能失败,导致订单事务回滚
}
// 子方法
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveLog(Log log) {
logMapper.insert(log);
}
关键点:
- 子方法失败只影响它自己的事务,父事务不受影响。
- 父事务后续失败回滚,子事务已提交的结果仍然保留。
- 审计日志一定会被保存,即使订单创建最终失败。
适用场景:那些必须独立持久化的操作,即使主业务流程失败也不能丢失。例如:
- 记录关键审计日志。
- 写入消息表(用于异步任务)。
- 保存操作通知记录。
NESTED:嵌套事务与局部回滚
行为:如果当前存在事务,则在当前事务内创建一个“嵌套事务”(通过数据库的保存点-Savepoint实现)。嵌套事务是外层事务的一部分,但可以独立回滚。
// 父方法
@Transactional
public void createOrder() {
orderMapper.insert(order);
try {
createGift(order.getId()); // 嵌套事务
} catch (Exception e) {
// 赠品创建失败,但订单主流程继续
log.warn("赠品创建失败,继续处理订单");
}
// 订单正常提交
}
// 子方法
@Transactional(propagation = Propagation.NESTED)
public void createGift(Long orderId) {
giftMapper.insert(gift);
}
关键点:
- 子方法回滚只回滚到保存点,不影响父方法已执行的操作。
- 父方法回滚会连同子方法一起回滚。
- 需要数据库支持保存点(如InnoDB支持,MyISAM不支持)。
适用场景:主流程必须继续,但某个非核心的子步骤允许“局部失败”。例如:
- 批量处理中,某一条失败不影响已成功处理的条目。
- 创建订单赠品、优惠券等可选功能。
注意:
- 需要使用
DataSourceTransactionManager(JPA的某些实现可能不支持)。
- 数据库存储引擎必须支持保存点。
三种常用传播行为对比
| 传播行为 |
事务关系 |
子方法失败的影响 |
父方法失败的影响 |
典型场景 |
REQUIRED |
共享同一事务 |
整个事务回滚 |
整个事务回滚 |
订单 + 订单商品 |
REQUIRES_NEW |
独立新事务 |
只回滚子事务 |
子事务已提交,不受影响 |
审计日志、操作记录 |
NESTED |
嵌套事务(保存点) |
回滚到保存点 |
整个事务回滚(含子操作) |
赠品、优惠券等可选功能 |
选型建议
- 默认使用
REQUIRED:绝大多数需要事务原子性的场景。
- 需要独立落盘的用
REQUIRES_NEW:如审计日志、必须记录的消息。
- 需要局部回滚的用
NESTED:如批处理中的单条记录、非核心的可选功能。
在我的测试项目中,这三种传播行为均按预期工作。理解并正确运用事务管理的传播机制,是构建健壮Java后端服务的关键。
几个要注意的地方
事务范围要尽可能小
// 不好的写法:将不需要事务的查询和计算也包含在事务内
@Transactional
public void process() {
List<Data> data = queryBigData(); // 慢查询,不需要事务
Data result = calculate(data); // 复杂计算,不需要事务
mapper.save(result); // 只有这里需要事务
}
// 改进写法:缩小事务边界
public void process() {
List<Data> data = queryBigData();
Data result = calculate(data);
saveInTransaction(result); // 将事务隔离在最小范围
}
@Transactional
private void saveInTransaction(Data data) {
mapper.save(data);
}
尽量只将数据库写操作包含在事务声明内。
批量插入务必使用 batchInsert
// 性能差:循环单次插入
@Transactional
public void save(List<Item> items) {
for (Item item : items) {
mapper.insert(item); // N次数据库网络IO
}
}
// 性能优:批量插入
@Transactional
public void save(List<Item> items) {
mapper.batchInsert(items); // 1次数据库网络IO
}
我曾忽略这一点,在处理1000条数据时,循环插入耗时约10秒,而改为批量插入后仅需0.5秒,性能差异巨大。这在处理MySQL等数据库时尤为重要。
长事务必须设置超时
@Transactional(timeout = 30)
public void longTask() {
// 复杂的长耗时业务逻辑
}
在生产环境中,为可能存在长事务的方法设置超时是必须的,可以防止某个事务长时间持有数据库锁,导致系统雪崩。
总结一下
这6种进阶的事务控制方案,各自针对特定的业务痛点:
- 编程式事务 → 解决如“库存不足时保留订单”这类需区分业务异常与系统故障的场景。
- @Transactional参数 → 明确隔离级别、超时、回滚规则,提升代码的健壮性与可移植性。
- 事务同步器 → 确保只有在事务提交成功后,才执行如发送MQ消息等副作用操作。
- 事务事件监听 → 通过发布-订阅模式,解耦核心业务逻辑与后续处理,提升代码可维护性。
- 手动控制事务 → 实现批量操作中“部分成功、部分失败”的精细化控制。
- 事务传播机制 → 灵活管理多个事务方法之间的边界,应对日志记录、嵌套操作等复杂场景。
对于80%的常规业务场景,简单的 @Transactional 注解足以应对。当遇到上述特殊需求时,再根据具体情况选择对应的高级方案即可。技术选型的核心是“够用就好”,避免过度设计。
代码在这里
完整的可运行示例代码与测试用例:
https://gitee.com/sh_wangwanbao/simple-transactional
数据库初始化脚本位于 doc/simple-transactional-init.sql,导入后即可运行所有测试。