news 2026/5/14 3:32:42

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/5/13 19:57:08

Zabbix使用飞书实现发送告警卡片[适用于zabbix 5.x版本]

说明:适用于5.x版本,5.x以上版本没测试。 参数如下: 名称 值 EVENT.DURATION {EVENT.DURATION} EVENTDATE {EVENT.DATE} EVENTNAME {EVENT.NAME} EVENTRECOVERYDATE {EVENT.RECOVERY.DATE} EVENTRECOVERYTIME {EVENT.RECOVERY.TIME} EVENTSTATUS {EVENT.STATUS} EVENTTIME {…

作者头像 李华
网站建设 2026/5/14 3:32:25

大数据领域Zookeeper的会话管理机制研究

大数据领域Zookeeper的会话管理机制研究 关键词:Zookeeper、会话管理、心跳机制、会话超时、临时节点、分布式协调、分布式系统 摘要:本文深入研究Apache Zookeeper的会话管理机制,系统解析会话生命周期、心跳通信协议、超时处理策略及其与分…

作者头像 李华
网站建设 2026/5/14 3:32:41

holiday 2026.02.06

1)以人为本,尊重女性同胞,员工是宝贵的资产 2)调整行政固定考核制度,调整生产弹性考核制度,鼓励为主,解决实际困难 3)加强处理投诉举报问题以及改进 4)加强滥用职权管…

作者头像 李华
网站建设 2026/5/9 9:55:57

Java赋能人工智能:JBoltAI框架基础AI能力深度调研

在人工智能(AI)技术日新月异的今天,Java作为一门历史悠久且广泛应用的编程语言,如何在这一浪潮中发挥其独特优势,成为众多开发者关注的焦点。JBoltAI框架的出现,为Java开发者提供了一个高效、稳定的AI应用开…

作者头像 李华