# simple-transactional **Repository Path**: zhh1234/simple-transactional ## Basic Information - **Project Name**: simple-transactional - **Description**: 研究Mall订单流程发现:@Transactional解决不了的5个痛点,可以用这6种高级用法搞定。 编程式事务区分业务失败和系统异常、事务同步器保证MQ一致性、事件监听解耦副作用、手动事务处理批量操作。 全部可运行代码+集成测试+流程图详解。花两天研究Mall源码+写demo+测试,希望能帮到你。 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 3 - **Created**: 2026-01-23 - **Last Updated**: 2026-01-23 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Spring事务进阶实战:6种用法全解析(附可运行代码) 看Mall项目订单代码时发现:一个方法操作6张表,14步业务逻辑,全在一个事务里,居然没炸。 研究了两天,发现了6种比`@Transactional`更灵活的玩法。写了个demo项目验证了一遍。 ## 我们要解决的痛点 日常开发中,`@Transactional`解决不了的几个问题: 1. **库存不足时**:想保留订单记录标记"待补货",但不知道怎么不回滚 2. **发MQ消息**:在事务里发了消息,结果事务回滚了,消息却发出去了 3. **批量操作**:100个订单发货,1个失败就全部回滚,但其实想让成功的继续 4. **记录日志**:业务失败了也想记录日志,但事务回滚了日志也没了 5. **隔离级别/超时**:不知道`@Transactional`那些参数怎么用 这篇文章会用实际代码演示6种解决方案。 ## 目录 - [编程式事务:区分业务失败和系统异常](#编程式事务区分业务失败和系统异常) - [@Transactional参数:隔离级别和超时](#transactional的参数我被坑过) - [事务同步器:提交后发MQ](#事务提交后发mq我之前都做错了) - [事务事件监听:解耦副作用操作](#事务事件监听解耦副作用操作) - [手动控制事务:批量操作](#批量操作必须用手动事务) - [事务传播机制:3种常用场景](#事务传播机制3种常用场景) ## 关于demo项目 **特点**:集成测试框架,通过反射自动构建参数,启动即测试,自动生成markdown报告。不用手动准备数据,不用一个个跑测试用例。 导入数据库脚本(doc/simple-transactional-init.sql),改下配置,启动项目就能看到完整测试结果。 --- ## 编程式事务:区分业务失败和系统异常 这是我在Mall里发现的一个场景:订单创建后要调用风控服务检查。 - **风控不通过**(业务规则):订单要保留,标记"待审核",人工复核 - **风控服务挂了**(系统故障):订单要回滚,不能留脏数据 **用`@Transactional`做不到**。因为它只能靠抛异常触发回滚,无法区分这两种情况。 ### TransactionTemplate可以动态控制 ```java public OrderResult createOrder(OrderParam param) { return transactionTemplate.execute(status -> { try { // 1. 创建订单 Order order = buildOrder(param); orderMapper.insert(order); // 2. 创建订单商品 List 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()); } }); } ``` ### 画个图就明白了 ```mermaid graph TD A[开始事务] --> B[创建订单] B --> C[创建订单商品] C --> D[锁定库存] D --> E[调用风控服务] E --> F{风控结果?} F -->|不通过-业务失败| G[更新订单状态=待审核] G --> H[提交事务] H --> I[订单保留,状态=待审核] F -->|通过| J[提交事务] J --> K[订单正常创建] F -->|服务异常-系统故障| L[setRollbackOnly] L --> M[回滚事务] M --> N[订单被删除] ``` ### 这才是编程式事务的价值 | 场景 | @Transactional | TransactionTemplate | |-----|---------------|---------------------| | 风控不通过 | 抛异常→全回滚 | 不回滚,保留订单 | | 风控服务挂了 | 抛异常→全回滚 | 回滚,不留脏数据 | | 库存不足 | 抛异常→全回滚 | 保留订单,标记"待补货" | **核心区别**:能区分"业务失败"和"系统异常",动态决定要不要回滚。 我测试了一下: ```bash # 测试风控不通过(高金额订单) POST /programmatic/risk-check # 结果 订单ID:8 订单状态:待审核 订单备注:风控检查未通过:金额过高 数据库:订单和商品明细都保留了 ``` 这玩意儿我之前真不知道能这么用。 ## @Transactional的参数,我被坑过 Mall的商品创建方法是这么写的: ```java @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`(读已提交) **如果代码里没显式指定隔离级别,换数据库就可能出问题。** 所以建议: ```java // 明确指定隔离级别,不依赖数据库默认值 @Transactional(isolation = Isolation.REPEATABLE_READ) public void someMethod() { // 避免环境切换导致行为变化 } ``` ### timeout和rollbackFor简单说两句 **timeout**:防止长事务锁表 ```java @Transactional(timeout = 30) // 30秒超时 public void complexTask() { // ... } ``` **rollbackFor**:Spring默认只有RuntimeException才回滚,Checked Exception不回滚 ```java @Transactional(rollbackFor = Exception.class) // 明确指定 public void createOrder() throws Exception { // ... } ``` 这两个参数记得加上,能避免很多坑。 ## 事务提交后发MQ,我之前都做错了 订单创建成功后,要发个MQ消息(30分钟后自动取消未支付订单)。 我之前是这么写的: ```java @Transactional public void createOrder() { orderMapper.insert(order); // 直接发MQ mqSender.send("order.cancel.delay", order.getId()); } ``` 看起来没问题吧?实际上有个致命问题。 ### 问题出在时机上 画个图就明白了: ```mermaid sequenceDiagram participant S as Service participant DB as Database participant MQ as RabbitMQ rect rgb(255, 240, 240) Note over S,MQ: 错误的做法 S->>DB: 1. INSERT订单 Note over DB: 数据在事务内
还没提交 S->>MQ: 2. 发送MQ消息 Note over MQ: 消息已发出 S->>DB: 3. 后面某步失败 DB-->>S: 4. 事务回滚 Note over DB: 订单被删除 Note over MQ,DB: 问题:消息发了
但数据没了 end ``` **问题本质**:MQ消息发出去了,但事务回滚了,订单根本不存在。30分钟后消费者去取消订单,发现订单不存在。 这就是**副作用的时机与事务一致性**问题: - 订单插入、库存扣减 → 在同一个事务里,要么全成功,要么全回滚 - MQ消息 → 不在这个事务里,发出去就收不回来了 ### 事务同步器解决这个问题 Spring提供了事务生命周期的钩子,让你在特定阶段执行回调: ```java @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消息不会发送"); } } } ); } ``` 现在的时序是这样: ```mermaid sequenceDiagram participant S as Service participant DB as Database participant MQ as RabbitMQ rect rgb(240, 255, 240) Note over S,MQ: 正确的做法 S->>DB: 1. INSERT订单 S->>S: 2. 注册afterCommit回调 alt 事务成功 S->>DB: 3. COMMIT Note over DB: 数据已持久化 S->>MQ: 4. 触发afterCommit
发送MQ消息 Note over MQ: 数据和消息一致 else 事务失败 S->>DB: 3. ROLLBACK Note over DB: 数据被删除 Note over S: afterCommit不执行 Note over MQ: 消息不会发送 end end ``` **核心区别**:只有订单真正提交到数据库后,才发MQ消息。事务回滚了,消息就不发。 ### 4个生命周期钩子 事务同步器提供了4个回调点: ```java 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); } } ); ``` 执行顺序是固定的: ```mermaid graph TD A[开始事务] --> B[业务逻辑执行] B --> C{要提交?} C -->|是| D[beforeCommit] D --> E[beforeCompletion] E --> F[COMMIT] F --> G[afterCommit] G --> H[afterCompletion状态COMMITTED] C -->|否| I[beforeCompletion] I --> J[ROLLBACK] J --> K[afterCompletion状态ROLLED_BACK] ``` ### 哪些场景必须用afterCommit 所有"对外的副作用"都应该放在afterCommit里: **场景1:发MQ消息** ```java @Override public void afterCommit() { // 延迟取消订单 mqSender.send("order.cancel.delay", orderId); } ``` **场景2:清理缓存** ```java @Override public void afterCommit() { // 清理商品缓存 redisTemplate.delete("product:" + productId); } ``` **场景3:记录日志到另一个库** ```java @Override public void afterCommit() { // 写到日志库(不在当前事务) logMapper.insert(businessLog); } ``` **场景4:调用外部服务** ```java @Override public void afterCommit() { // 通知第三方 thirdPartyService.notify(order); } ``` **核心原则**:只有订单数据真正持久化了,外部世界才能知道。 ### 同库的日志也要用afterCommit吗? 理论上,如果日志表和订单表在同一个数据库、同一个事务里,写早了会一起回滚,不会有问题。 但实际业务中,我们希望: 1. **解耦**:订单业务和日志记录分离 2. **性能**:日志操作不影响主事务耗时 3. **重试**:日志失败可以独立重试,不影响订单 所以建议还是放在afterCommit里。 ### 我测试了一下 ```bash # 运行测试 POST /synchronization/phases # 控制台输出 【阶段1-beforeCommit】事务即将提交 【阶段2-beforeCompletion】事务即将完成 【阶段3-afterCommit】事务已提交 【阶段4-afterCompletion】事务已完成,状态:提交 MQ消息已发送 ``` 顺序是固定的,非常可靠。 ## 事务事件监听:解耦副作用操作 订单创建成功后,要做3件事:发MQ、记录日志、发通知。 如果都写在一个方法里,代码会很臃肿: ```java @Transactional public void createOrder() { orderMapper.insert(order); // 业务逻辑越来越多 mqSender.send(...); logService.save(...); notifyService.send(...); } ``` 而且事务范围太大了,发短信也在事务里? ### 事务事件监听可以解耦 **第1步:定义事件** ```java @Getter @AllArgsConstructor public class OrderCreatedEvent { private String orderSn; private Long memberId; private BigDecimal amount; } ``` **第2步:发布事件** ```java @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步:监听事件** ```java @Component public class OrderEventListener { // 关键:@TransactionalEventListener + 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`本身与事务无关,但监听器的执行时机取决于监听方式。** 画个图说明: ```mermaid sequenceDiagram participant S as OrderService participant E as EventPublisher participant L1 as @EventListener
普通监听器 participant L2 as @TransactionalEventListener
AFTER_COMMIT participant DB as Database rect rgb(255, 245, 240) Note over S,DB: 方法有 @Transactional S->>DB: 1. INSERT订单 S->>E: 2. publishEvent(event) E->>L1: 3. 立即同步调用 Note over L1: 普通监听器立即执行
此时事务还没提交 E->>L2: 4. 事件挂起 Note over L2: AFTER_COMMIT监听器不执行
等待事务提交 alt 事务提交成功 S->>DB: 5. COMMIT DB->>L2: 6. 触发AFTER_COMMIT Note over L2: 监听器执行
数据已持久化 else 事务回滚 S->>DB: 5. ROLLBACK Note over L2: AFTER_COMMIT不执行 end end ``` **关键区别**: | 监听方式 | 执行时机 | 事务回滚影响 | |---------|---------|------------| | @EventListener | 立即执行 | 已执行的副作用无法撤销 | | @TransactionalEventListener(AFTER_COMMIT) | 事务提交后 | 事务回滚则不执行 | | @TransactionalEventListener(AFTER_ROLLBACK) | 事务回滚后 | 只有回滚才执行 | ### 4个事务阶段 ```java // 提交前(做最后校验) @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. 必须在事务方法里发布** ```java // 错误:方法没有 @Transactional public void createOrder() { orderMapper.insert(order); eventPublisher.publishEvent(event); // AFTER_COMMIT监听器不会触发! } // 正确:方法有 @Transactional @Transactional public void createOrder() { orderMapper.insert(order); eventPublisher.publishEvent(event); // 监听器会在提交后触发 } ``` **2. 子事务的事件跟随子事务** ```java @Transactional public void parentMethod() { // 父事务 childMethod(); // 子事务(REQUIRES_NEW) } @Transactional(propagation = Propagation.REQUIRES_NEW) public void childMethod() { orderMapper.insert(order); eventPublisher.publishEvent(event); // 监听器跟随子事务的提交 } ``` **3. 如果没有事务怎么办** ```java // 监听器默认不执行,除非加 fallbackExecution=true @TransactionalEventListener( phase = TransactionPhase.AFTER_COMMIT, fallbackExecution = true // 没有事务也会执行 ) public void handleEvent(OrderCreatedEvent event) { // ... } ``` ### 对比事务同步器 | 方式 | 代码耦合度 | 扩展性 | 适用场景 | |-----|----------|--------|---------| | TransactionSynchronization | 高(在方法里注册) | 低 | 简单场景,1-2个操作 | | @TransactionalEventListener | 低(发布订阅) | 高 | 复杂场景,多个操作 | **我的建议**: - 只有1-2个操作,用TransactionSynchronization - 有多个操作,或者可能扩展,用@TransactionalEventListener 好处是代码解耦了,要加新功能,写个监听器就行,不用改原方法。 ## 批量操作必须用手动事务 批量发货100个订单,其中1个失败了咋办? 如果用`@Transactional`: ```java @Transactional public void batchDelivery(List orderIds) { for (Long orderId : orderIds) { // 发货逻辑 } } ``` 问题:100个订单在一个事务里,1个失败全部回滚。 但实际需求是:**成功的正常发货,失败的记录下来。** ### 用PlatformTransactionManager手动控制 ```java @Service public class OrderBatchService { @Autowired private PlatformTransactionManager transactionManager; public BatchResult batchDelivery(List orderIds) { List success = new ArrayList<>(); List 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); } } ``` ### 高级用法:设置事务属性 对于定时任务、后台批处理这种场景,可以显式控制事务属性: ```java public BatchResult batchCloseOrder(List 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个失败。成功的都发货了,失败的记录下来了。 **核心价值**:每条数据独立事务,部分失败不影响其他。 ## 事务传播机制:3种常用场景 创建订单时,要调另一个方法插入订单商品。两个方法都有`@Transactional`,会咋样? **7种传播机制**,常用的是3种:REQUIRED、REQUIRES_NEW、NESTED。 ### REQUIRED(默认):同成同败 **行为**:有事务就加入,没有就新建。父子方法共享同一个事务。 ```java // 父方法 @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); } ``` **关键点**: - 父子方法在同一个事务里 - 子方法抛异常 → 整个事务回滚(父也一起回滚) - 订单和订单商品"同成同败" ```mermaid graph LR A[createOrder开启事务] --> B[insert order] B --> C[createOrderItems加入事务] C --> D[insert items] D --> E{子方法异常?} E -->|是| F[整个事务回滚] E -->|否| G[事务提交] ``` **适用场景**:一个业务流程内的多步骤需要"同成同败"。80%的场景都用这个。 ### REQUIRES_NEW:独立事务 **行为**:挂起当前事务,开启一个全新的事务,独立提交/回滚。 ```java // 父方法 @Transactional public void createOrder() { orderMapper.insert(order); logService.saveLog(log); // 新事务,独立提交 // 后面的代码可能失败 } // 子方法 @Transactional(propagation = Propagation.REQUIRES_NEW) public void saveLog(Log log) { logMapper.insert(log); } ``` **关键点**: - 子方法失败只影响子事务,父事务不受影响 - 父事务后续回滚,子事务已提交的结果也保留 - 日志一定会保存,即使订单创建失败 ```mermaid graph TD A[createOrder事务1] --> B[insert order] B --> C[挂起事务1] C --> D[saveLog开启事务2] D --> E[insert log] E --> F[事务2提交-日志已保存] F --> G[恢复事务1] G --> H{事务1继续执行} H -->|成功| I[事务1提交] H -->|失败| J[事务1回滚-但日志保留] ``` **适用场景**:必须独立持久化的动作,如: - 记录审计日志 - 写消息表 - 发送通知记录 即使主流程失败也不能丢。 ### NESTED:局部回滚 **行为**:在同一物理事务内使用"保存点"(Savepoint),子方法相当于子事务。 ```java // 父方法 @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支持) ```mermaid graph TD A[createOrder事务开启] --> B[insert order] B --> C[创建保存点] C --> D[createGift嵌套事务] D --> E{赠品创建} E -->|失败| F[回滚到保存点] F --> G[订单保留] G --> H[事务提交] E -->|成功| I[继续执行] I --> H ``` **适用场景**:主流程可继续,但某个子步骤允许"局部失败回滚"。 - 批处理中某条失败不影响前面已写入的步骤 - 赠品、优惠券等可选功能 **注意**: - 需要使用`DataSourceTransactionManager`(JPA的不支持) - 数据库必须支持保存点(InnoDB支持,MyISAM不支持) ### 三种传播行为对比 | 传播行为 | 事务关系 | 子方法失败影响 | 父方法失败影响 | 典型场景 | |---------|---------|--------------|--------------|---------| | REQUIRED | 共享事务 | 整个事务回滚 | 整个事务回滚 | 订单+订单商品 | | REQUIRES_NEW | 独立事务 | 只回滚子事务 | 子事务已提交 | 审计日志 | | NESTED | 保存点 | 回滚到保存点 | 整个事务回滚 | 赠品、优惠券 | ### 选型建议 - **默认用REQUIRED**:80%的场景都是"同成同败" - **需要独立落盘的用REQUIRES_NEW**:审计日志、消息表 - **需要局部回滚的用NESTED**:可选功能、批处理 我测试了一下,这3种传播行为都符合预期。 ## 几个要注意的地方 ### 事务范围要小 ```java // 不好的写法 @Transactional public void process() { List data = queryBigData(); // 慢查询,不需要事务 Data result = calculate(data); // 计算,不需要事务 mapper.save(result); // 真正需要事务 } // 改成这样 public void process() { List data = queryBigData(); Data result = calculate(data); saveInTransaction(result); } @Transactional private void saveInTransaction(Data data) { mapper.save(data); } ``` 只把写操作放事务里。 ### 批量插入要用batchInsert ```java // 慢 @Transactional public void save(List items) { for (Item item : items) { mapper.insert(item); // N次数据库访问 } } // 快 @Transactional public void save(List items) { mapper.batchInsert(items); // 1次数据库访问 } ``` 我之前不知道这个,踩过坑。1000条数据,循环插入要10秒,批量插入只要0.5秒。 ### 长事务要设置超时 ```java @Transactional(timeout = 30) public void longTask() { // 防止锁表 } ``` 生产环境一定要加这个。 ## 总结一下 这6种玩法,每个都能解决实际问题: 1. **编程式事务** → 库存不足保留订单 2. **@Transactional参数** → 隔离级别、超时、回滚规则 3. **事务同步器** → 事务提交后发MQ 4. **事务事件监听** → 解耦业务逻辑 5. **手动控制事务** → 批量操作 6. **事务传播机制** → 日志记录、赠品创建 80%的场景,`@Transactional`就够了。遇到特殊情况,再用对应的高级用法。 别过度设计,够用就行。 ## 代码在这里 所有代码都是可以跑的,有完整测试用例。 数据库脚本在 doc/simple-transactional-init.sql,导入就能用。 --- **你们平时用Spring事务都遇到过什么坑?** **或者有什么好的实践经验?** **欢迎在评论区聊聊,我也想学习学习。** 特别是事务传播机制那块,我自己还没完全搞透。如果有大佬愿意指点一下,那就太好了。 --- **如果这篇文章对你有帮助,麻烦点个赞👍,让更多人看到。** 这篇文章从研究Mall源码到写demo,再到写文章、画图、测试,前后花了两天时间。 希望能帮你解决实际问题。 --- 下一篇我准备写《6张表的订单事务,它凭什么不炸?》,深入分析Mall的订单流程设计。 感兴趣的话可以关注一下。