网站建设内容策略wordpress 导航网站
2026/1/27 18:14:12 网站建设 项目流程
网站建设内容策略,wordpress 导航网站,百度加速乐wordpress,忘记wordpress的账号和密码Transactional做不到的5件事#xff0c;我用这6种方法解决了 看Mall项目订单代码时发现#xff1a;一个方法操作6张表#xff0c;14步业务逻辑#xff0c;全在一个事务里#xff0c;居然没炸。 研究了两天#xff0c;发现了6种比Transactional更灵活的玩法。写了个demo…Transactional做不到的5件事我用这6种方法解决了看Mall项目订单代码时发现一个方法操作6张表14步业务逻辑全在一个事务里居然没炸。研究了两天发现了6种比Transactional更灵活的玩法。写了个demo项目验证了一遍。我们要解决的痛点日常开发中Transactional解决不了的几个问题库存不足时想保留订单记录标记待补货但不知道怎么不回滚发MQ消息在事务里发了消息结果事务回滚了消息却发出去了批量操作100个订单发货1个失败就全部回滚但其实想让成功的继续记录日志业务失败了也想记录日志但事务回滚了日志也没了隔离级别/超时不知道Transactional那些参数怎么用这篇文章会用实际代码演示6种解决方案。目录编程式事务区分业务失败和系统异常Transactional参数隔离级别和超时事务同步器提交后发MQ事务事件监听解耦副作用操作手动控制事务批量操作事务传播机制3种常用场景编程式事务区分业务失败和系统异常这是我在Mall里发现的一个场景订单创建后要调用风控服务检查。风控不通过业务规则订单要保留标记待审核人工复核风控服务挂了系统故障订单要回滚不能留脏数据用Transactional做不到。因为它只能靠抛异常触发回滚无法区分这两种情况。TransactionTemplate可以动态控制java体验AI代码助手代码解读复制代码public OrderResult createOrder(OrderParam param) { return transactionTemplate.execute(status - { try { // 1. 创建订单 Order order buildOrder(param); orderMapper.insert(order); // 2. 创建订单商品 ListOrderItem 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()); } }); }画个图就明白了不通过-业务失败通过服务异常-系统故障开始事务创建订单创建订单商品锁定库存调用风控服务风控结果?更新订单状态待审核提交事务订单保留,状态待审核提交事务订单正常创建setRollbackOnly回滚事务订单被删除这才是编程式事务的价值场景TransactionalTransactionTemplate风控不通过抛异常→全回滚不回滚保留订单风控服务挂了抛异常→全回滚回滚不留脏数据库存不足抛异常→全回滚保留订单标记待补货核心区别能区分业务失败和系统异常动态决定要不要回滚。我测试了一下bash体验AI代码助手代码解读复制代码# 测试风控不通过高金额订单 POST /programmatic/risk-check # 结果 订单ID8 订单状态待审核 订单备注风控检查未通过金额过高 数据库订单和商品明细都保留了这玩意儿我之前真不知道能这么用。Transactional的参数我被坑过Mall的商品创建方法是这么写的java体验AI代码助手代码解读复制代码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体验AI代码助手代码解读复制代码// 明确指定隔离级别不依赖数据库默认值 Transactional(isolation Isolation.REPEATABLE_READ) public void someMethod() { // 避免环境切换导致行为变化 }timeout和rollbackFor简单说两句timeout防止长事务锁表java体验AI代码助手代码解读复制代码Transactional(timeout 30) // 30秒超时 public void complexTask() { // ... }rollbackForSpring默认只有RuntimeException才回滚Checked Exception不回滚java体验AI代码助手代码解读复制代码Transactional(rollbackFor Exception.class) // 明确指定 public void createOrder() throws Exception { // ... }这两个参数记得加上能避免很多坑。事务提交后发MQ我之前都做错了订单创建成功后要发个MQ消息30分钟后自动取消未支付订单。我之前是这么写的java体验AI代码助手代码解读复制代码Transactional public void createOrder() { orderMapper.insert(order); // 直接发MQ mqSender.send(order.cancel.delay, order.getId()); }看起来没问题吧实际上有个致命问题。问题出在时机上画个图就明白了ServiceDatabaseRabbitMQ错误的做法数据在事务内还没提交消息已发出订单被删除问题消息发了但数据没了1. INSERT订单2. 发送MQ消息3. 后面某步失败4. 事务回滚ServiceDatabaseRabbitMQ问题本质MQ消息发出去了但事务回滚了订单根本不存在。30分钟后消费者去取消订单发现订单不存在。这就是副作用的时机与事务一致性问题订单插入、库存扣减 → 在同一个事务里要么全成功要么全回滚MQ消息 → 不在这个事务里发出去就收不回来了事务同步器解决这个问题Spring提供了事务生命周期的钩子让你在特定阶段执行回调java体验AI代码助手代码解读复制代码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消息不会发送); } } } ); }现在的时序是这样ServiceDatabaseRabbitMQ正确的做法数据已持久化数据和消息一致数据被删除afterCommit不执行消息不会发送alt[事务成功][事务失败]1. INSERT订单2. 注册afterCommit回调3. COMMIT4. 触发afterCommit发送MQ消息3. ROLLBACKServiceDatabaseRabbitMQ核心区别只有订单真正提交到数据库后才发MQ消息。事务回滚了消息就不发。4个生命周期钩子事务同步器提供了4个回调点java体验AI代码助手代码解读复制代码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); } } );执行顺序是固定的是否开始事务业务逻辑执行要提交?beforeCommitbeforeCompletionCOMMITafterCommitafterCompletion状态COMMITTEDbeforeCompletionROLLBACKafterCompletion状态ROLLED_BACK哪些场景必须用afterCommit所有对外的副作用都应该放在afterCommit里场景1发MQ消息java体验AI代码助手代码解读复制代码Override public void afterCommit() { // 延迟取消订单 mqSender.send(order.cancel.delay, orderId); }场景2清理缓存java体验AI代码助手代码解读复制代码Override public void afterCommit() { // 清理商品缓存 redisTemplate.delete(product: productId); }场景3记录日志到另一个库java体验AI代码助手代码解读复制代码Override public void afterCommit() { // 写到日志库不在当前事务 logMapper.insert(businessLog); }场景4调用外部服务java体验AI代码助手代码解读复制代码Override public void afterCommit() { // 通知第三方 thirdPartyService.notify(order); }核心原则只有订单数据真正持久化了外部世界才能知道。同库的日志也要用afterCommit吗理论上如果日志表和订单表在同一个数据库、同一个事务里写早了会一起回滚不会有问题。但实际业务中我们希望解耦订单业务和日志记录分离性能日志操作不影响主事务耗时重试日志失败可以独立重试不影响订单所以建议还是放在afterCommit里。我测试了一下bash体验AI代码助手代码解读复制代码# 运行测试 POST /synchronization/phases # 控制台输出 【阶段1-beforeCommit】事务即将提交 【阶段2-beforeCompletion】事务即将完成 【阶段3-afterCommit】事务已提交 【阶段4-afterCompletion】事务已完成状态提交 MQ消息已发送顺序是固定的非常可靠。事务事件监听解耦副作用操作订单创建成功后要做3件事发MQ、记录日志、发通知。如果都写在一个方法里代码会很臃肿java体验AI代码助手代码解读复制代码Transactional public void createOrder() { orderMapper.insert(order); // 业务逻辑越来越多 mqSender.send(...); logService.save(...); notifyService.send(...); }而且事务范围太大了发短信也在事务里事务事件监听可以解耦第1步定义事件java体验AI代码助手代码解读复制代码Getter AllArgsConstructor public class OrderCreatedEvent { private String orderSn; private Long memberId; private BigDecimal amount; }第2步发布事件java体验AI代码助手代码解读复制代码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体验AI代码助手代码解读复制代码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本身与事务无关但监听器的执行时机取决于监听方式。画个图说明OrderServiceEventPublisherEventListener普通监听器TransactionalEventListenerAFTER_COMMITDatabase方法有 Transactional普通监听器立即执行此时事务还没提交AFTER_COMMIT监听器不执行等待事务提交监听器执行数据已持久化AFTER_COMMIT不执行alt[事务提交成功][事务回滚]1. INSERT订单2. publishEvent(event)3. 立即同步调用4. 事件挂起5. COMMIT6. 触发AFTER_COMMIT5. ROLLBACKOrderServiceEventPublisherEventListener普通监听器TransactionalEventListenerAFTER_COMMITDatabase关键区别监听方式执行时机事务回滚影响EventListener立即执行已执行的副作用无法撤销TransactionalEventListener(AFTER_COMMIT)事务提交后事务回滚则不执行TransactionalEventListener(AFTER_ROLLBACK)事务回滚后只有回滚才执行4个事务阶段java体验AI代码助手代码解读复制代码// 提交前做最后校验 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体验AI代码助手代码解读复制代码// 错误方法没有 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体验AI代码助手代码解读复制代码Transactional public void parentMethod() { // 父事务 childMethod(); // 子事务REQUIRES_NEW } Transactional(propagation Propagation.REQUIRES_NEW) public void childMethod() { orderMapper.insert(order); eventPublisher.publishEvent(event); // 监听器跟随子事务的提交 }3. 如果没有事务怎么办java体验AI代码助手代码解读复制代码// 监听器默认不执行除非加 fallbackExecutiontrue TransactionalEventListener( phase TransactionPhase.AFTER_COMMIT, fallbackExecution true // 没有事务也会执行 ) public void handleEvent(OrderCreatedEvent event) { // ... }对比事务同步器方式代码耦合度扩展性适用场景TransactionSynchronization高在方法里注册低简单场景1-2个操作TransactionalEventListener低发布订阅高复杂场景多个操作我的建议只有1-2个操作用TransactionSynchronization有多个操作或者可能扩展用TransactionalEventListener好处是代码解耦了要加新功能写个监听器就行不用改原方法。批量操作必须用手动事务批量发货100个订单其中1个失败了咋办如果用Transactionaljava体验AI代码助手代码解读复制代码Transactional public void batchDelivery(ListLong orderIds) { for (Long orderId : orderIds) { // 发货逻辑 } }问题100个订单在一个事务里1个失败全部回滚。但实际需求是成功的正常发货失败的记录下来。用PlatformTransactionManager手动控制java体验AI代码助手代码解读复制代码Service public class OrderBatchService { Autowired private PlatformTransactionManager transactionManager; public BatchResult batchDelivery(ListLong orderIds) { ListLong success new ArrayList(); ListString 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体验AI代码助手代码解读复制代码public BatchResult batchCloseOrder(ListLong 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体验AI代码助手代码解读复制代码// 父方法 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); }关键点父子方法在同一个事务里子方法抛异常 → 整个事务回滚父也一起回滚订单和订单商品同成同败是否createOrder开启事务insert ordercreateOrderItems加入事务insert items子方法异常?整个事务回滚事务提交适用场景一个业务流程内的多步骤需要同成同败。80%的场景都用这个。REQUIRES_NEW独立事务行为挂起当前事务开启一个全新的事务独立提交/回滚。java体验AI代码助手代码解读复制代码// 父方法 Transactional public void createOrder() { orderMapper.insert(order); logService.saveLog(log); // 新事务独立提交 // 后面的代码可能失败 } // 子方法 Transactional(propagation Propagation.REQUIRES_NEW) public void saveLog(Log log) { logMapper.insert(log); }关键点子方法失败只影响子事务父事务不受影响父事务后续回滚子事务已提交的结果也保留日志一定会保存即使订单创建失败成功失败createOrder事务1insert order挂起事务1saveLog开启事务2insert log事务2提交-日志已保存恢复事务1事务1继续执行事务1提交事务1回滚-但日志保留适用场景必须独立持久化的动作如记录审计日志写消息表发送通知记录即使主流程失败也不能丢。NESTED局部回滚行为在同一物理事务内使用保存点Savepoint子方法相当于子事务。java体验AI代码助手代码解读复制代码// 父方法 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支持失败成功createOrder事务开启insert order创建保存点createGift嵌套事务赠品创建回滚到保存点订单保留事务提交继续执行适用场景主流程可继续但某个子步骤允许局部失败回滚。批处理中某条失败不影响前面已写入的步骤赠品、优惠券等可选功能注意需要使用DataSourceTransactionManagerJPA的不支持数据库必须支持保存点InnoDB支持MyISAM不支持三种传播行为对比传播行为事务关系子方法失败影响父方法失败影响典型场景REQUIRED共享事务整个事务回滚整个事务回滚订单订单商品REQUIRES_NEW独立事务只回滚子事务子事务已提交审计日志NESTED保存点回滚到保存点整个事务回滚赠品、优惠券选型建议默认用REQUIRED80%的场景都是同成同败需要独立落盘的用REQUIRES_NEW审计日志、消息表需要局部回滚的用NESTED可选功能、批处理我测试了一下这3种传播行为都符合预期。几个要注意的地方事务范围要小java体验AI代码助手代码解读复制代码// 不好的写法 Transactional public void process() { ListData data queryBigData(); // 慢查询不需要事务 Data result calculate(data); // 计算不需要事务 mapper.save(result); // 真正需要事务 } // 改成这样 public void process() { ListData data queryBigData(); Data result calculate(data); saveInTransaction(result); } Transactional private void saveInTransaction(Data data) { mapper.save(data); }只把写操作放事务里。批量插入要用batchInsertjava体验AI代码助手代码解读复制代码// 慢 Transactional public void save(ListItem items) { for (Item item : items) { mapper.insert(item); // N次数据库访问 } } // 快 Transactional public void save(ListItem items) { mapper.batchInsert(items); // 1次数据库访问 }我之前不知道这个踩过坑。1000条数据循环插入要10秒批量插入只要0.5秒。长事务要设置超时java体验AI代码助手代码解读复制代码Transactional(timeout 30) public void longTask() { // 防止锁表 }生产环境一定要加这个。总结一下这6种玩法每个都能解决实际问题编程式事务→ 库存不足保留订单Transactional参数→ 隔离级别、超时、回滚规则事务同步器→ 事务提交后发MQ事务事件监听→ 解耦业务逻辑手动控制事务→ 批量操作事务传播机制→ 日志记录、赠品创建80%的场景Transactional就够了。遇到特殊情况再用对应的高级用法。别过度设计够用就行。代码在这里所有代码都是可以跑的有完整测试用例。数据库脚本在 doc/simple-transactional-init.sql导入就能用。你们平时用Spring事务都遇到过什么坑或者有什么好的实践经验欢迎在评论区聊聊我也想学习学习。特别是事务传播机制那块我自己还没完全搞透。如果有大佬愿意指点一下那就太好了。如果这篇文章对你有帮助麻烦点个赞让更多人看到。这篇文章从研究Mall源码到写demo再到写文章、画图、测试前后花了两天时间。希望能帮你解决实际问题

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询