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

Spring Boot 中事务(Transaction)的正确使用姿势

目录

    • 前言
    • 一、什么是事务?
      • 一句话定义
      • 直观理解(转账例子)
    • 二、事务解决了什么问题?
    • 三、事务的四大特性(ACID)
    • 四、Spring 中事务是如何实现的?
      • 使用方式
      • 本质原理(非常重要)
    • 五、事务应该加在哪里?(核心设计问题)
      • ✅ 正确做法(99% 场景)
      • ❌ 错误做法一:每个方法都加事务
      • ❌ 错误做法二:apply 不加事务,内部方法加
    • 六、为什么 `this.xxx()` 会导致事务失效?
      • 1️⃣ 同一个类中的 this 调用
      • 2️⃣ 正确写法:通过代理对象调用
        • 方式一:从容器获取 Bean
        • 方式二(推荐):自注入代理对象
    • 七、同一个类 vs 不同类调用事务方法
      • 1️⃣ 同一个类中
      • 2️⃣ 不同类中(推荐结构)
    • 八、事务方法内部能不能用 this 调用其他方法?
      • ✅ 可以,而且是正确的
      • ⚠️ 前提条件
    • 九、什么时候内部方法也要加 @Transactional?
      • 示例:日志必须单独提交
    • 十、使用事务时必须注意的 6 个坑
    • 十一、实战:如何实现“全成功或全回滚”?
    • 十二、总结

前言

在 Spring Boot 项目中,事务是保证数据一致性的核心机制之一。
但同时,它也是**最容易“看起来写了,其实没生效”**的功能。

本文将围绕以下问题展开:

  • 什么是事务?解决什么问题?
  • @Transactional到底是怎么生效的?
  • 事务应该加在“哪里”才是对的?
  • 为什么this.xxx()会导致事务失效?
  • 同一个类、不同类调用事务方法的区别
  • 真实项目中事务的最佳实践

一、什么是事务?

一句话定义

事务是一组操作,要么全部成功,要么全部失败回滚。


直观理解(转账例子)

转账操作至少包含两步:

  1. A 账户扣钱
  2. B 账户加钱

这两个操作:

  • 不能只成功一个
  • 必须作为一个整体成功或失败

这就是事务存在的意义。


二、事务解决了什么问题?

事务主要解决三类核心问题:

问题没事务会发生什么
数据一致性只成功一半
异常回滚出错后数据已落库
并发安全并发修改产生脏数据

三、事务的四大特性(ACID)

这是原理,但理解比背更重要。

  • A(原子性):要么全成功,要么全失败
  • C(一致性):事务前后数据状态合法
  • I(隔离性):并发事务互不干扰
  • D(持久性):提交后数据不会丢失

四、Spring 中事务是如何实现的?

使用方式

Spring 中最常见的事务用法:

@TransactionalpublicvoiddoBusiness(){// 数据库操作}

本质原理(非常重要)

Spring 的事务是通过 AOP + 代理对象实现的

执行流程大致是:

  1. 调用代理对象方法
  2. 方法执行前 → 开启事务
  3. 方法正常结束 → 提交事务
  4. 方法抛异常 → 回滚事务

关键前提:必须是通过“代理对象”调用方法。


五、事务应该加在哪里?(核心设计问题)

假设你有如下业务:

publicvoidapply(Commandcommand){queryData();deleteData();saveData();commitData();}

目标是:

apply 方法里的所有数据库操作,要么全成功,要么全回滚


✅ 正确做法(99% 场景)

@Transactional(rollbackFor=Exception.class)publicvoidapply(Commandcommand){queryData();deleteData();saveData();commitData();}

只在“业务入口方法”上加事务。


❌ 错误做法一:每个方法都加事务

@TransactionalpublicvoidqueryData(){}@TransactionalpublicvoidsaveData(){}

问题:

  • 事务边界被拆碎
  • 业务完整性不清晰
  • 维护成本高

❌ 错误做法二:apply 不加事务,内部方法加

publicvoidapply(){saveData();// 提交commitData();// 抛异常}

结果:

  • 前面的数据已经提交
  • 无法整体回滚

六、为什么this.xxx()会导致事务失效?

这是 Spring 事务最经典的坑。

1️⃣ 同一个类中的 this 调用

publicvoidaa(){this.apply();// ❌}@Transactionalpublicvoidapply(){}

事务不会生效。原因:

  • this调用绕过了 Spring 代理
  • AOP 无法介入

Spring 的事务是基于 AOP(面向切面编程) 实现的。当你为一个方法加上 @Transactional 时,Spring 会生成一个 代理对象(Proxy)。

外部调用:当其他类调用 apply 时,实际上调用的是代理对象,代理对象会先开启事务,再执行业务逻辑。

内部调用(this):如果你在类内部直接 this.apply(),由于 this 指向的是原始对象而不是代理对象,Spring 根本没机会介入。结果就是:事务失效。


2️⃣ 正确写法:通过代理对象调用

方式一:从容器获取 Bean
applicationContext.getBean(this.getClass()).apply(command);

applicationContext.getBean(this.getClass()).apply(command)的目的就是强制从 Spring 容器中获取当前类的代理对象,确保调用路径经过 Spring 的拦截器,从而让事务生效。

方式二(推荐):自注入代理对象
@AutowiredprivateApplyServiceself;publicvoidaa(){self.apply();}

七、同一个类 vs 不同类调用事务方法

1️⃣ 同一个类中

publicvoidaa(){this.apply();// ❌}

👉 必须通过代理对象调用(applicationContext.getBean(this.getClass()).apply()或者self.apply()


2️⃣ 不同类中(推荐结构)

@ServicepublicclassAService{@AutowiredprivateBServicebService;publicvoidaa(){bService.apply();}}@ServicepublicclassBService{@Transactionalpublicvoidapply(){}}

事务天然生效


八、事务方法内部能不能用 this 调用其他方法?

✅ 可以,而且是正确的

@Transactionalpublicvoidapply(){this.queryData();this.saveData();}

原因:

  • 事务已经在apply()入口开启
  • 内部方法共享同一个事务

⚠️ 前提条件

内部方法没有单独的事务语义

也就是说:

publicvoidsaveData(){}

而不是:

@TransactionalpublicvoidsaveData(){}// ⚠️

九、什么时候内部方法也要加 @Transactional?

只有在明确的事务语义不同时才需要。

示例:日志必须单独提交

@Transactionalpublicvoidapply(){saveMainData();logService.saveLog();// 即使 apply 失败,也要保存thrownewRuntimeException();}@Transactional(propagation=REQUIRES_NEW)publicvoidsaveLog(){}

十、使用事务时必须注意的 6 个坑

  1. ❌ 同一类中使用this.xxx()调用事务方法
  2. ❌ 异常被 try-catch 吃掉
  3. ❌ 默认不回滚Exception
  4. @Transactional加在private方法
  5. ❌ 事务中开启新线程
  6. ❌ 数据库引擎不支持事务(如 MyISAM)

十一、实战:如何实现“全成功或全回滚”?

假设你有一个 apply 方法,内部包含多个数据库操作:

@ServicepublicclassApplyService{@AutowiredprivateApplyServiceself;publicvoidaa(Commandcommand){self.apply(command);}@Transactional(rollbackFor=Exception.class)// 在入口方法加注解publicvoidapply(Commandcommand){// 操作1:直接写 SQLuserMapper.delete(...);userMapper.insert(...);// 操作2:调用类内部的其他方法,这些子方法会运行在 apply 开启的同一个事务中this.queryData();this.deleteData();this.saveData();this.commitData();}privatevoidqueryData(){}privatevoiddeleteData(){}privatevoidsaveData(){}privatevoidcommitData(){}}

结论:
入口最重要:只要 apply 是通过代理对象调用的,事务就会开启。

内部方法用 this 没问题:一旦事务开启,该线程就已经绑定了数据库连接。apply 内部通过 this 调用 saveData,这些操作都会自动加入到 apply 的事务中。

不要在子方法上乱加注解:除非你需要特殊的传播机制(比如无论如何都要记录日志),否则子方法不需要重复加 @Transactional。

十二、总结

事务应该定义在“业务入口方法”上,
必须通过 Spring 代理对象调用;
一旦进入事务方法,内部普通方法直接调用即可。


http://www.jsqmd.com/news/351118/

相关文章:

  • TypeScript/JavaScript 中的异步迭代语句
  • 一文读懂:传统RAG、多模态RAG与Agent的本质区别与联系,收藏级技术解析
  • 基于SpringBoot + Vue的自驾游攻略查询系统
  • 微信小程序Python-uniapp儿童疫苗接种预约医疗提醒系统
  • CANN生态深度解析:ops-nn仓库的算子实现与性能优化
  • 【收藏必备】颠覆Skills!新型Agent自己造工具开源,零技能起步性能碾压Gemini 3 Pro
  • 速看!AI应用架构师如何运用AI驱动质量管理降本增效
  • js中的生成器函数
  • SAP核心模块单据关系及关键数据表详解
  • 微信小程序Python-uniapp基于Android的全民健身App设计与实现
  • 地平线征程 6 工具链入门教程 | 征程 6B 计算平台部署指南
  • 微信小程序Python-uniapp 小区果蔬商城
  • Vibe Coding 与 LangChain、LangGraph 的协同进化
  • 代码生成超越 GPT-4:DeepSeek-V4 编程任务实战与 2026 开发者效率提升指南
  • 微信小程序Python-uniapp 游戏攻略系统 逃跑吧!少年的游戏角色介绍系统
  • 【毕设】基于人脸识别的实验室智能门禁系统的设计与实现
  • 微信小程序Python-uniapp 演唱会售票系统
  • 2026低成本训练趋势:DeepSeek复刻V4训练管线,低成本实现模型微调实战
  • 大数据领域Zookeeper的会话管理机制研究
  • AI原生应用架构设计:何时使用模型蒸馏?
  • 微信小程序Python-uniapp 智能包裹配送服务管理系统
  • 图解网络26 - 指南
  • 微信小程序Python-uniapp 校园财递通快递代取系统的设计与实现
  • 微信小程序Python-uniapp 消防知识学习平台系统
  • DeepSeek总结的PostgreSQL解码GIF文件SQL移植到DuckDB的性能优化方法
  • 基于Spring Boot的企业采购管理系统的设计与实现
  • 如何通过管理中心发布Teams应用
  • holiday 2026.02.06
  • 比较好的入户门品牌有哪些?2026十大品牌综合实力深度评测解析 - 匠言榜单
  • 数据行业六大岗位详解+AI大模型入门到进阶学习路线_AI大模型时代下的数据行业