当前位置: 首页 > article >正文

数据库事务的坑:@Transactional注解的隐藏陷阱

一、问题现场还原那是一个月黑风高的夜晚小王正准备下班突然运营群里炸了【运营】重大bug用户下单成功了但没扣库存 【运营】已有多名用户反馈... 【运维】涉及金额已达12,580...小王赶紧打开代码Service public class OrderService { Autowired private OrderMapper orderMapper; Autowired private InventoryService inventoryService; Transactional(rollbackFor Exception.class) public void createOrder(OrderDTO order) { // 1. 创建订单 Order orderEntity new Order(); orderEntity.setUserId(order.getUserId()); orderEntity.setAmount(order.getAmount()); orderMapper.insert(orderEntity); // 插入成功 // 2. 扣减库存 inventoryService.decreaseStock(order.getSkuId(), order.getQuantity()); // ❌ 如果这里抛异常订单已经插入了但库存没扣 // 3. 发送消息 messageService.sendOrderCreatedMessage(orderEntity.getId()); } } Service public class InventoryService { public void decreaseStock(String skuId, Integer quantity) { // 扣减库存逻辑 // ... if (库存不足) { throw new RuntimeException(库存不足); } } }问题分析虽然createOrder加了Transactional但inventoryService.decreaseStock()是this调用自调用不走Spring的代理所以事务根本没有生效二、原因剖析Spring事务的代理机制2.1 Spring事务基于AOP代理┌─────────────────────────────────────────────────────────┐ │ Spring IOC容器 │ ├─────────────────────────────────────────────────────────┤ │ │ │ ┌──────────────────┐ ┌──────────────────┐ │ │ │ OrderService │ │ InventoryService│ │ │ │ ┌────────────┐ │ │ │ │ │ │ │ createOrder │ │ │ │ │ │ │ │ Transactional│ │ │ │ │ │ └─────┬──────┘ │ │ │ │ │ │ │ │ │ │ │ │ │ ▼ │ │ │ │ │ │ ┌────────────┐ │ │ │ │ │ │ │ 事务代理 │ │ │ │ │ │ │ │ (AOP) │ │ │ │ │ │ │ └────────────┘ │ │ │ │ │ └────────┬─────────┘ └──────────────────┘ │ │ │ │ │ ▼ │ │ ┌──────────────────┐ │ │ │ decreaseStock() │ ← this调用不经过代理 │ │ └──────────────────┘ │ │ │ └─────────────────────────────────────────────────────────┘2.2 自调用失效的原因当我们在同一个类中调用另一个方法时public void methodA() { this.methodB(); // this.methodB() 不会走代理 }Spring的事务是通过AOP代理实现的只有外部调用才会经过代理内部调用自调用会直接跳过代理。2.3 Transactional失效的场景汇总场景示例是否生效自调用this.method()❌ 不生效private方法Transactional private method()❌ 不生效异常被catchtry { } catch { }❌ 不生效非RuntimeExceptionthrow new Exception()❌ 不生效默认只回滚RuntimeException多数据源未指定两个DataSource⚠️ 需要指定transactionManager三、解决方案让事务”生效”方案一注入自身推荐Service public class OrderService { Autowired private OrderService self; // 注入自身 Transactional(rollbackFor Exception.class) public void createOrder(OrderDTO order) { // 1. 创建订单 orderMapper.insert(orderEntity); // 2. 扣减库存 - 通过代理调用 self.decreaseStockInTransaction(order.getSkuId(), order.getQuantity()); // 3. 发送消息 messageService.sendOrderCreatedMessage(orderEntity.getId()); } Transactional(rollbackFor Exception.class) public void decreaseStockInTransaction(String skuId, Integer quantity) { // 扣减库存逻辑 inventoryMapper.decreaseStock(skuId, quantity); } }方案二使用TransactionTemplateService public class OrderService { Autowired private TransactionTemplate transactionTemplate; public void createOrder(OrderDTO order) { transactionTemplate.executeWithoutResult(status - { // 1. 创建订单 orderMapper.insert(orderEntity); // 2. 扣减库存 inventoryService.decreaseStock(skuId, quantity); // 3. 发送消息 messageService.sendOrderCreatedMessage(orderEntity.getId()); }); } }方案三使用AopContext.currentProxy()SpringBootApplication(exposeProxy true) // 需要开启暴露代理 EnableAspectJAutoProxy(exposeProxy true) public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } } Service public class OrderService { public void createOrder(OrderDTO order) { ((OrderService) AopContext.currentProxy()) .decreaseStockInTransaction(skuId, quantity); } Transactional(rollbackFor Exception.class) public void decreaseStockInTransaction(String skuId, Integer quantity) { // ... } }方案四确保异常能被正确感知Transactional(rollbackFor Exception.class) // 明确指定回滚条件 public void createOrder(OrderDTO order) { try { inventoryService.decreaseStock(skuId, quantity); } catch (Exception e) { log.error(扣减库存失败, e); // 不要吞掉异常否则事务不会回滚 throw e; // 重新抛出或者不catch } }四、事务传播行为详解4.1 七种传播行为public enum Propagation { REQUIRED, // 如果当前有事务加入该事务默认 REQUIRES_NEW, // 开启新事务挂起当前事务 SUPPORTS, // 如果有事务加入事务没有则以非事务执行 NOT_SUPPORTED, // 以非事务执行挂起当前事务 MANDATORY, // 必须在事务中执行否则抛异常 NEVER, // 必须在非事务中执行否则抛异常 NESTED // 嵌套事务 Savepoint }4.2 常见场景选择Service public class UserService { Autowired private AccountService accountService; Transactional public void registerUser(User user) { // 1. 创建用户 - 使用当前事务 userMapper.insert(user); // 2. 初始化账户 - 单独事务失败不影响用户创建 accountService.initAccountWithNewTransaction(user.getId()); // 3. 发送欢迎邮件 - 非事务失败不影响主流程 emailService.sendWelcomeEmail(user.getEmail()); // NOT_SUPPORTED } } Service public class AccountService { Transactional(propagation Propagation.REQUIRES_NEW) public void initAccountWithNewTransaction(Long userId) { // 这个方法会开启新事务 // 即使这里失败UserService的事务也不会回滚 accountMapper.initAccount(userId); } } Service public class EmailService { Transactional(propagation Propagation.NOT_SUPPORTED) public void sendWelcomeEmail(String email) { // 以非事务执行失败不影响主流程 } }五、排查工具事务Debug5.1 开启事务日志# application.yml logging: level: org.springframework.orm.jpa: DEBUG org.springframework.transaction: DEBUG5.2 使用Transactional注解剖析Configuration public class TransactionAspectConfig { Bean public BeanFactoryTransactionAnnotationParser transactionAnnotationParser() { return new BeanFactoryTransactionAnnotationParser(); } public boolean isTransactional(Method method) { Transactional tx method.getAnnotation(Transactional.class); return tx ! null; } }5.3 事务超时配置Transactional(timeout 30) // 30秒超时 public void createOrder(OrderDTO order) { // 如果超过30秒自动回滚 }六、预防措施最佳实践清单6.1 事务使用检查表✅ Transactional使用检查 ├── 1. 是否在public方法上private方法不生效 ├── 2. 是否是外部调用自调用需要通过代理 ├── 3. 异常是否被catch吞掉 ├── 4. 是否指定了rollbackFor默认只回滚RuntimeException ├── 5. 是否有多个数据源是否指定了transactionManager ├── 6. 是否需要配置事务超时 └── 7. 传播行为是否正确6.2 编码规范建议// 建议1不要在事务方法中进行远程调用 Transactional public void createOrder(OrderDTO order) { // ❌ 不好远程调用在事务中事务时间过长 remoteService.call(); // ✅ 好先完成本地事务再异步调用远程 } // 建议2大事务拆分 Transactional public void createOrder(OrderDTO order) { // 保持事务简短 orderMapper.insert(order); } // 异步执行其他操作 Async public void afterOrderCreated(OrderDTO order) { messageService.sendMessage(order); inventoryService.decreaseStock(order.getSkuId(), order.getQuantity()); }七、总结今天我们学到了要点说明问题本质Spring事务基于代理自调用不走代理失效场景private方法、自调用、异常被catch、非RuntimeException解决方案注入自身、TransactionTemplate、AopContext.currentProxy()传播行为REQUIRED默认、REQUIRES_NEW开启新事务最佳实践保持事务简短、避免远程调用在事务中彩蛋小王最后用了”注入自身”的方案修复了bug。他在周会上分享经验时说道“Spring的事务就像高考监考——只有从外部监考老师看过去才是有效的。你自己看着自己考试那不就作弊了吗”

相关文章:

数据库事务的坑:@Transactional注解的隐藏陷阱

一、问题现场还原 那是一个月黑风高的夜晚,小王正准备下班,突然运营群里炸了: 【运营】重大bug!用户下单成功了,但没扣库存! 【运营】已有多名用户反馈... 【运维】涉及金额已达¥12,580... 小…...

手把手教你用Qwen-Image:小白也能轻松制作带文字的创意海报

手把手教你用Qwen-Image:小白也能轻松制作带文字的创意海报 你是不是也遇到过这样的烦恼?想为活动做个海报,脑子里有画面,但打开设计软件就傻眼——字体怎么选?排版怎么弄?背景图去哪找?折腾半…...

Apache SeaTunnel Web 初体验:从零开始搭建大数据流处理可视化平台(含避坑指南)

Apache SeaTunnel Web 初体验:从零开始搭建大数据流处理可视化平台(含避坑指南) 作为一名长期与命令行打交道的数据工程师,第一次接触Apache SeaTunnel Web时,那种"终于不用再记复杂参数"的解脱感至今难忘。…...

LaserGRBL激光雕刻软件终极指南:从零开始掌握专业雕刻技巧

LaserGRBL激光雕刻软件终极指南:从零开始掌握专业雕刻技巧 【免费下载链接】LaserGRBL Laser optimized GUI for GRBL 项目地址: https://gitcode.com/gh_mirrors/la/LaserGRBL LaserGRBL是一款专为GRBL控制器优化的专业激光雕刻软件,通过直观的图…...

3步掌握微信数据解密:本地安全解密方案的终极指南

3步掌握微信数据解密:本地安全解密方案的终极指南 【免费下载链接】WechatDecrypt 微信消息解密工具 项目地址: https://gitcode.com/gh_mirrors/we/WechatDecrypt 当微信聊天记录被加密存储在数据库中,你是否曾感到束手无策?那些珍贵…...

一道KMP统考真题彻底讲透:nextval与滑动距离的本质皆

一、各自优势和对比 这是检索出来的数据,据说是根据第三方评测与企业数据,三款产品在代码生成质量上各有侧重: 产品 语言优势 场景亮点 核心差异 百度 Comate C核心代码质量第一;Python首生成率达92.3% SQL生成准确率提升35%&…...

SEAL库CKKS实战:手把手教你调参避开‘scale out of bounds’报错(附8192模数配置)

SEAL库CKKS实战:手把手教你调参避开‘scale out of bounds’报错(附8192模数配置) 在同态加密的实际应用中,微软SEAL库的CKKS方案因其支持浮点数运算的特性而备受开发者青睐。然而,许多初入门的开发者在尝试实现复杂计…...

5个高效技巧:掌握EmojiOne彩色表情字体完全指南

5个高效技巧:掌握EmojiOne彩色表情字体完全指南 【免费下载链接】emojione-color OpenType-SVG font of EmojiOne 2.3 项目地址: https://gitcode.com/gh_mirrors/em/emojione-color EmojiOne Color是一款由Adobe开发的开源彩色字体,采用OpenType…...

Cursor Pro免费激活终极指南:突破API限制的完整技术解决方案

Cursor Pro免费激活终极指南:突破API限制的完整技术解决方案 【免费下载链接】cursor-free-vip [Support 0.45](Multi Language 多语言)自动注册 Cursor Ai ,自动重置机器ID , 免费升级使用Pro 功能: Youve reached yo…...

智赋学术・真实赋能|虎贲等考 AI:全流程论文写作辅助平台,以真文献・真数据・真工具重构学术创作

虎贲等考 AI 智能写作(https://www.aihbdk.com/)是一款基于人工智能深度模型研发的论文写作辅助工具,专注服务于本专科、硕士、博士等各阶段学生与科研人员,以全流程覆盖、真实学术资源、硬核实证工具、高度合规安全为核心定位&am…...

Shell 脚本:别让你的自动化变成“自爆化”

太长不看版(老鸟)脚本头:#!/bin/bash 写死,别用 #!/bin/sh(坑太多)。调试:bash -x script.sh 能看到每一行执行过程。变量引用:永远用双引号包起来 "$var",否则…...

macOS Monterey安装OpenClaw避坑指南:千问3.5-9B适配

macOS Monterey安装OpenClaw避坑指南:千问3.5-9B适配 1. 为什么选择OpenClaw千问3.5-9B组合 去年换装M1 Max芯片的MacBook Pro后,我一直在寻找能充分发挥ARM架构性能的本地AI方案。直到遇见OpenClaw这个开源的自动化智能体框架,配合千问3.5…...

WebStorm高效开发Vue3+TypeScript项目:配置与实战技巧

1. WebStorm与Vue3TypeScript开发环境搭建 WebStorm作为JetBrains旗下的前端开发利器,对Vue3和TypeScript的支持堪称完美。最新版本甚至内置了Volar语言服务,让类型推断和代码补全更加精准。先说说我的踩坑经历:第一次用WebStorm创建Vue3项目…...

DAMO-YOLO TinyNAS模型评估全攻略:mAP/PR曲线

DAMO-YOLO TinyNAS模型评估全攻略:mAP/PR曲线 1. 为什么模型评估比训练更重要 刚跑通DAMO-YOLO TinyNAS的训练流程时,很多人会直接跳到部署环节,觉得“能出结果就行”。但实际项目中,我见过太多团队在交付前才发现模型在真实场景…...

当AI学会“动手”,架构师如何为它“刹车”?

当AI Agent开始自主执行文件读写、邮件收发、系统操作,你交给它的权限,到底是“效率工具”还是“失控炸弹”? 2026年开年,OpenClaw引爆了AI Agent领域——大模型从“会聊天”迈入“能行动”的时代。Meta安全专家的一条指令&#x…...

【网络安全】从零开始:15种常见网络攻击类型及防御措施全解析,小白必备!建议收藏学习!

【网络安全】从零开始:15种常见网络攻击类型及防御措施全解析,小白必备!建议收藏学习! 随着攻击者效率和复杂性的提高,网络犯罪每年都在急剧增加。[网络攻击]的发生有多种不同的原因和多种不同的方式。但是&#xff0c…...

IMX6ULL开发板实战:NFS挂载报错No route to host的5种修复方法

IMX6ULL开发板NFS挂载故障排查指南:从"No route to host"到稳定连接 嵌入式开发过程中,NFS挂载几乎是每位开发者都会遇到的基础操作。但当开发板突然提示"No route to host"时,那种调试过程中的挫败感我深有体会——明明…...

InfixPDFEditor:解决PDF文本编辑与添加水印的实用指南

在日常办公中,你是否收到过一份PDF合同,发现里面有一个错别字却无法修改;或者需要给几十页的PDF文件批量加上公司logo水印,却只能一页页截图;又或者需要对比两个版本的PDF文档差异,肉眼逐行比对眼睛都快看花…...

从“词元”到“符元”:Token中文定名的再思考——以概念精确性与长期稳定性为视角

近日,全国科学技术名词审定委员会发布公告,推荐将人工智能领域中的“Token”译为“词元”,并面向社会试用。随后,《人民日报》发文《专家解读token中文名为何定为“词元”》,对这一命名从专业角度进行了系统阐释。文中…...

云原生应用开发最佳实践:构建现代化的云原生系统

云原生应用开发最佳实践:构建现代化的云原生系统 前言 作为一个在数据深渊里捞了十几年 Bug 的女码农,我深知云原生应用开发在现代企业中的重要性。随着云技术的快速发展,传统的应用开发方式已经难以满足需求。今天,我就来聊聊云原…...

WinISO:解决光盘镜像编辑与制作的三大实际问题

在日常工作中,你是否遇到过这样的场景:下载了一个 ISO 镜像文件,想往里面添加几个补丁或删除一个无用文件,却只能解压后再重新打包;或者你有一个旧版 Windows 安装盘,想替换其中的 install.wim 文件来制作集…...

SITS2026平台深度拆解:如何用1套配置实现92%业务场景零代码交付?(附Gartner验证的ROI测算模型)

第一章:SITS2026平台深度拆解:如何用1套配置实现92%业务场景零代码交付?(附Gartner验证的ROI测算模型) 2026奇点智能技术大会(https://ml-summit.org) SITS2026并非传统低代码平台的简单迭代,而是基于语义…...

AI驱动的知识管理平台构建全路径(从零到生产级上线的12个关键决策点)

第一章:AI原生软件研发知识管理平台的范式跃迁 2026奇点智能技术大会(https://ml-summit.org) 传统知识管理平台以文档为中心,依赖人工归档、关键词检索与静态权限控制,难以应对AI原生研发中高频迭代、多模态产出(如提示工程日志…...

ROS2 Humble下Cartographer纯定位不成功?别急,可能是你的.lua配置文件少了这行关键代码

ROS2 Humble下Cartographer纯定位失败的深度排查与解决方案 当你在RViz中看到地图显示正常,但激光雷达点云始终无法与地图正确匹配时,那种挫败感我深有体会。去年在部署仓库AGV项目时,我花了整整三天时间排查类似问题,最终发现是.…...

【仅限SITS2026参会者解封】:AI微服务弹性扩缩容决策引擎设计手册(含动态负载预测模型Python实现+K8s HPA自定义指标CRD YAML)

第一章:SITS2026分享:AI原生微服务架构设计 2026奇点智能技术大会(https://ml-summit.org) 在SITS2026现场,来自全球头部AI基础设施团队的实践者共同提出“AI原生微服务”范式——它并非传统微服务的简单迁移,而是围绕模型生命周…...

从稀疏重构到精准定位:l1-SVD算法的核心思想与工程实现

1. 稀疏信号重构与DOA估计的困境 想象你站在一个嘈杂的会议室里,试图通过几个麦克风确定说话人的方位。这就是DOA(波达方向)估计的典型场景。传统方法如MUSIC算法在理想环境下表现优异,但当信源间距过小或快拍数不足时&#xff0c…...

如何高效掌握DeepONet:5步快速上手非线性算子深度学习实战指南

如何高效掌握DeepONet:5步快速上手非线性算子深度学习实战指南 【免费下载链接】deeponet Learning nonlinear operators via DeepONet based on the universal approximation theorem of operators 项目地址: https://gitcode.com/gh_mirrors/de/deeponet D…...

数码管展示

文章目录文章目录1.数码管显示6个91.1 效果图展示1.2 代码2.数码管显示2个72.1 效果图展示2.2 代码3.数码管轮播显示6位3.1 效果图展示3.2 代码4.数码管轮播显示2位4.1 效果图展示4.2 代码5.数码管显示0-55.1 效果图展示6.思考题6.1如何显示数码管1-6轮播6.1.1 效果图展示6.1.2…...

如何在Switch上使用Xbox和PlayStation手柄?sys-con让您的第三方控制器焕发新生

如何在Switch上使用Xbox和PlayStation手柄?sys-con让您的第三方控制器焕发新生 【免费下载链接】sys-con Nintendo Switch sysmodule that allows support for third-party controllers 项目地址: https://gitcode.com/gh_mirrors/sy/sys-con 您是否曾想过&a…...

OpenClaw 太难装了?试试 LangTARS:一行命令部署 + WebUI 管理面板,还能接入 Dify/Coze/nn??孛

1. 什么是 Apache SeaTunnel? Apache SeaTunnel 是一个非常易于使用、高性能、支持实时流式和离线批处理的海量数据集成平台。它的目标是解决常见的数据集成问题,如数据源多样性、同步场景复杂性以及资源消耗高的问题。 核心特性 丰富的数据源支持&#…...