news 2026/7/1 9:29:51

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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 代理对象调用;
一旦进入事务方法,内部普通方法直接调用即可。


版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/1 23:00:30

基于SpringBoot + Vue的自驾游攻略查询系统

文章目录前言一、详细操作演示视频二、具体实现截图三、技术栈1.前端-Vue.js2.后端-SpringBoot3.数据库-MySQL4.系统架构-B/S四、系统测试1.系统测试概述2.系统功能测试3.系统测试结论五、项目代码参考六、数据库代码参考七、项目论文示例结语前言 💛博主介绍&#…

作者头像 李华
网站建设 2026/7/1 23:01:55

C语言对话-28.Contracts, Promises, and Mere Semantics

taodm翻译和大多数日子一样,我开始了那天的工作-在我的方形房间内,端着新鲜的咖啡,在开始写代码前,正收着早上的email。很奇特,它这天,Guru没有突然出现在我身后。实际上,我无意中听…

作者头像 李华
网站建设 2026/7/1 18:32:15

0欧电阻作用

一、调试与测试预留调试接口:方便测试电路电流(串联后临时拆下接电流表)。参数调试:在匹配电路不确定时先贴0Ω,调试后更换为具体阻值元件。功能跳线:通过贴或不贴来决定线路是否接通,用于版本兼…

作者头像 李华
网站建设 2026/7/1 18:31:45

天辛大师也谈预测未来学,AI时代的指数级进化浪潮

被誉为当代思想智者的天辛大师,近日在一场汇聚了各界精英的高端论坛上,再次将目光投向了人类文明发展的前沿——未来学,并深入探讨了AI时代所掀起的指数级进化浪潮。天辛大师以其深邃的洞察力和对人类命运的深切关怀,为我们勾勒出…

作者头像 李华
网站建设 2026/7/1 18:29:41

CANN绿色计算:AIGC推理能效优化实战指南

cann组织链接:https://atomgit.com/cann ops-nn仓库链接:https://atomgit.com/cann/ops-nn 当单次Stable Diffusion生成消耗0.0012度电,当百万级AIGC服务日均碳排放超百吨——能效已成为AIGC规模化落地的“隐形天花板”。本文将首次揭秘CANN如…

作者头像 李华
网站建设 2026/7/1 16:15:59

MindSpeed LLM适配Qwen3-Coder-Next并上线魔乐社区,训练推理教程请查收

MindSpeed LLM作为昇腾AI生态的重要技术支撑,专为大规模语言模型设计,具有超强的计算能力和灵活的开发支持。Qwen3-Coder-Next一发布,MindSpeed LLM框架立刻支持跑通。MindSpeed LLM快速部署与应用Qwen3-Coder-Next的教程已上线魔乐社区&…

作者头像 李华