Spring 中的事务
🧾 一、什么是事务?
🧠 通俗理解:
事务 = 一组操作,要么全部成功,要么全部失败,不能只做一半。
比如你转账:
- A 账户扣钱
- B 账户加钱
如果 A 扣了钱但 B 没收到,那就出问题了!这就是 数据不一致,事务就是为了解决这类问题。
✅ 二、事务的四大特性(ACID)
| 特性 | 解释 |
|---|---|
| 原子性(Atomicity) | 要么全部成功,要么全部失败 |
| 一致性(Consistency) | 操作前后数据处于一致状态 |
| 隔离性(Isolation) | 多个事务互不干扰 |
| 持久性(Durability) | 提交后的数据是永久保存的 |
💡 三、Spring 中的事务管理方式
Spring 支持两种事务管理方式:
1. 编程式事务管理(不常用)
手动写事务边界,代码控制事务提交/回滚。
TransactionStatus status = txManager.getTransaction(...);
try {// 业务代码txManager.commit(status);
} catch (Exception e) {txManager.rollback(status);
}
🚫 缺点:代码侵入性强、重复、易出错。实际开发中不推荐。
2. 声明式事务管理 ✅(主流)
通过注解或配置来管理事务,干净、简洁、优雅!
@Service
public class AccountService {@Transactionalpublic void transfer() {// 扣钱// 加钱// 如果中间出异常,自动回滚}
}
📌 四、@Transactional 注解详解
@Transactional 是 Spring 中声明事务的注解,作用范围可以是类或方法。
常用属性:
| 属性 | 含义 |
|---|---|
propagation | 事务传播行为(默认 REQUIRED) |
isolation | 事务隔离级别 |
rollbackFor | 指定哪些异常触发回滚(默认是运行时异常) |
readOnly | 是否只读事务 |
timeout | 设置事务超时时间 |
🔄 五、事务的传播行为(重点)
| 行为 | 含义 |
|---|---|
REQUIRED(默认) | 有事务就加入,没有就新建 |
REQUIRES_NEW | 每次都新建一个事务,原事务挂起 |
NESTED | 嵌套事务,有独立的回滚点 |
SUPPORTS | 有事务就用,没有就不用 |
NOT_SUPPORTED | 不支持事务,挂起当前事务 |
NEVER | 当前不能存在事务,否则抛异常 |
MANDATORY | 必须存在事务,否则抛异常 |
🔒 六、事务隔离级别(对应数据库的)
| 隔离级别 | 解决的问题 | 说明 |
|---|---|---|
DEFAULT | 使用数据库默认 | |
READ_UNCOMMITTED | 脏读 | 最低,性能高但不安全 |
READ_COMMITTED | 不可重复读 | 常用,如 Oracle 默认 |
REPEATABLE_READ | 幻读 | MySQL 默认 |
SERIALIZABLE | 最强隔离 | 安全但性能差 |
💣 七、事务生效的注意事项(易踩坑)
🚨 1. 事务方法必须是 public
@Transactional // 正确
public void doSomething() {}
🚨 2. 方法 不能是同一个类中内部调用
// 错误示例:事务不起作用
public void outer() {this.inner(); // 没经过代理
}
✅ 正确做法:通过代理调用
- 注入自身(
@Lazy方式) - 拆到 Service 层让 Spring 来代理调用
🚨 3. 默认只对 运行时异常 回滚
@Transactional(rollbackFor = Exception.class)
🧪 八、事务管理器(Spring 内部机制)
Spring 通过 PlatformTransactionManager 接口进行统一管理。
常用实现类:
| 实现类 | 场景 |
|---|---|
DataSourceTransactionManager | 使用 JDBC、MyBatis |
JpaTransactionManager | 使用 JPA/Hibernate |
ChainedTransactionManager | 多数据源事务 |
✅ 九、一句话总结
Spring 的事务机制让你只关心业务逻辑,不用手动管理事务,声明式的方式简洁高效,是真正的企业级利器。
@Transactional 实现原理
它的实现原理非常巧妙,实际上是通过 AOP(面向切面编程) 和 代理机制 实现的。这一机制让你无需写一行事务管理的代码,Spring 会自动为你处理。
我们一起来详细剖析一下底层原理!🧠
💡 一、核心原理概览
1. AOP + 代理:
@Transactional 注解是基于 AOP(面向切面编程)实现的,具体来说,Spring 会为标记了 @Transactional 注解的方法创建一个代理对象,这个代理对象会在方法执行前后进行事务的开启、提交和回滚等处理。
2. 动态代理:
Spring 通过动态代理技术(JDK 代理或 CGLIB 代理)生成一个代理对象,这个对象会拦截你的方法调用,在方法调用前后进行事务管理的相关操作。
3. 事务管理器:
Spring 会利用配置的 事务管理器(PlatformTransactionManager) 来处理事务的开启、提交、回滚等操作。
🛠️ 二、@Transactional 的工作流程
步骤 1:创建代理对象
- 代理方式:Spring 使用 JDK 动态代理 或 CGLIB 代理 来生成代理对象。默认情况下,如果目标类实现了接口,Spring 会使用 JDK 动态代理;如果没有实现接口,则使用 CGLIB 创建子类代理。
步骤 2:事务管理器的配置
@Transactional依赖于 事务管理器 来执行事务操作,Spring 根据你配置的PlatformTransactionManager来管理事务。常见的事务管理器有:DataSourceTransactionManager:用于 JDBC。JpaTransactionManager:用于 JPA(如 Hibernate)。HibernateTransactionManager:用于 Hibernate。
步骤 3:方法执行前,开启事务
当你调用被 @Transactional 注解标记的方法时,Spring 代理会拦截这个方法的调用,首先会判断当前方法是否需要开启新事务(即,方法的传播行为是 REQUIRED)。如果需要开启新事务,则会通过 事务管理器 启动一个新的事务。
- 事务的传播行为(
propagation):比如,REQUIRED表示如果当前没有事务,就新建一个事务,如果已有事务,就加入到当前事务中。
步骤 4:方法执行中,进行事务操作
方法执行过程中,Spring 会维持该事务的状态,直到方法执行完毕。
- 事务隔离级别(
isolation):隔离级别决定了事务之间如何互相影响(比如,是否允许脏读、不可重复读等)。
步骤 5:方法执行后,提交或回滚事务
- 方法正常完成:如果方法执行没有异常,Spring 会在方法结束后提交事务。
- 方法出现异常:如果方法抛出异常,Spring 会根据
@Transactional配置的异常回滚规则,判断是否回滚该事务。
⚙️ 三、事务管理的关键组件
1. TransactionInterceptor:核心事务拦截器
- Spring 使用
TransactionInterceptor来处理所有带有@Transactional注解的方法,它是事务管理的关键拦截器。 - 该拦截器负责从容器中获取事务管理器、根据事务配置设置事务属性(如隔离级别、传播行为等),并且控制事务的开启、提交和回滚。
2. TransactionProxyFactoryBean:生成代理对象
TransactionProxyFactoryBean是 Spring 用来创建事务代理的工厂,实际上它会根据注解或配置生成一个代理对象。- 这个代理对象会拦截对事务方法的调用,并在调用方法前后进行事务的处理。
3. PlatformTransactionManager:事务管理器
- 事务管理器(如
DataSourceTransactionManager)负责实际的事务操作,包括开启事务、提交事务、回滚事务等。
🔍 四、事务处理的实际步骤
- 方法调用被代理对象拦截
- 被
@Transactional标记的方法会被 AOP 代理拦截,调用TransactionInterceptor。
- 被
- 事务拦截器执行逻辑
TransactionInterceptor会根据@Transactional配置,从容器中获取 事务管理器(PlatformTransactionManager)。
- 判断事务传播行为
- 如果当前没有事务(例如没有正在进行的事务),根据传播行为决定是否新建事务。默认是
REQUIRED,即如果没有事务,创建一个新的事务。
- 如果当前没有事务(例如没有正在进行的事务),根据传播行为决定是否新建事务。默认是
- 开启事务
- 通过
PlatformTransactionManager开启一个事务(实际是对底层数据库操作的封装)。
- 通过
- 执行目标方法
- 目标方法执行,在方法内的操作都在同一个事务中进行。
- 提交或回滚事务
- 如果方法执行没有抛出异常,Spring 提交事务;如果抛出指定异常(如运行时异常),Spring 会回滚事务。
💡 五、示例:@Transactional 事务的底层工作
假设你有一个服务类 AccountService,方法 transfer() 被 @Transactional 注解标记:
@Service
public class AccountService {@Transactionalpublic void transfer() {// 扣款操作// 加款操作// 如果中间出错,Spring 会自动回滚}
}
- Spring 会生成一个代理对象
AccountService,拦截transfer()方法调用。 - 代理对象通过
TransactionInterceptor来控制事务的开启、提交或回滚。 - 在调用
transfer()方法之前,Spring 会检查是否需要开启事务,创建事务并将其绑定到当前线程(一般是通过ThreadLocal来绑定)。 transfer()执行完后,如果没有异常,Spring 会提交事务;如果出现异常,Spring 会根据配置回滚事务。
🧠 六、总结
@Transactional的底层原理依赖于 AOP + 动态代理,通过 事务拦截器(TransactionInterceptor)和 事务管理器(PlatformTransactionManager)来实现事务的控制。Spring 提供了灵活的事务传播行为和隔离级别,让事务控制变得更加简单和清晰。
Spring 事务在什么情况下会失效?
1. 方法是 private 或 protected
Spring 的事务管理是基于 AOP(面向切面编程) 实现的,而 AOP 需要代理对象来执行方法。如果方法是 private 或 protected,Spring 的 AOP 代理是无法拦截到该方法的,因此事务无法生效。
解决方法:
- 将事务方法设置为
public,这样 Spring 的代理对象才能正确地拦截并应用事务。
@Transactional
public void transfer() { // 必须是 public// 业务逻辑
}
2. 在同一类内调用事务方法
当一个类内部的事务方法相互调用时,事务是不会生效的。这是因为 事务代理 是基于代理对象的,而类内部方法调用是直接调用对象的方法,不会经过代理。
例子:
@Service
public class AccountService {@Transactionalpublic void transfer() {// 扣款操作this.otherMethod(); // 调用同一个类中的方法}@Transactionalpublic void otherMethod() {// 加款操作}
}
在这个例子中,transfer() 调用了 otherMethod(),但 @Transactional 注解没有生效,因为调用 this.otherMethod() 是直接调用类内部的方法,不会经过代理。
解决方法:
- 拆分方法,将事务性方法提取到另一个服务类中,确保 Spring 代理对象能够接管方法调用。
3. 没有配置事务管理器
Spring 中的事务是依赖于 PlatformTransactionManager 来进行管理的。如果没有配置或注入正确的事务管理器,事务就无法生效。
解决方法:
- 确保在
applicationContext.xml或Spring Boot配置类中配置了正确的事务管理器。
Spring Boot 示例:
@Configuration
@EnableTransactionManagement
public class TransactionConfig {@Beanpublic PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {return new JpaTransactionManager(entityManagerFactory);}
}
4. 事务方法抛出了非 RuntimeException 异常
Spring 默认只对 运行时异常 (RuntimeException 和其子类) 回滚事务。如果你在方法中抛出了一个 检查型异常(checked exception)(如 IOException、SQLException 等),默认情况下,Spring 不会回滚事务。
解决方法:
- 使用
@Transactional的rollbackFor属性来指定哪些异常需要回滚事务。
@Transactional(rollbackFor = Exception.class) // 指定回滚异常
public void transfer() throws IOException {// 业务逻辑
}
5. 事务被 @Transactional 注解的代理方法外部调用
Spring 的事务管理依赖于 代理对象,如果事务方法被其他 非 Spring 管理的对象调用,Spring 的事务也不会生效。
解决方法:
- 确保事务方法仅通过 Spring 管理的代理对象来调用。避免手动调用
new创建的对象或外部非 Spring 管理的对象。
6. 事务的传播行为不正确
事务的传播行为(propagation)控制了事务的嵌套和传播方式。如果设置不当,事务可能不会按预期工作。
例如,如果你使用了 REQUIRES_NEW 传播行为,每次方法都会启动一个新的事务,原事务会被挂起。如果这时原事务出现问题,它就无法回滚。
解决方法:
- 确保事务的传播行为与业务场景相匹配。常见的设置是
REQUIRED(默认),它会加入当前事务,如果没有事务,则创建一个新的事务。
@Transactional(propagation = Propagation.REQUIRED)
public void transfer() {// 事务逻辑
}
7. 非事务方法调用事务方法时,事务失效
如果一个非事务方法调用了带有 @Transactional 的方法,在一些情况下事务不会生效。特别是在 非 Spring 管理的对象 上,事务不会生效。
解决方法:
- 确保事务方法是由 Spring 管理的代理对象来调用,避免在非 Spring 管理的对象中调用事务方法。
8. 只读事务修改数据
@Transactional 中的 readOnly 属性告诉 Spring 该事务是只读的。如果在只读事务中执行了写操作(比如更新数据库),在某些数据库系统中事务可能不会按预期提交,甚至会抛出异常。
解决方法:
- 确保只读事务只用于查询操作,避免在只读事务中执行写操作。
@Transactional(readOnly = true)
public List<User> getAllUsers() {return userRepository.findAll();
}
🚦 九、总结
Spring 事务失效的常见原因:
- 事务方法是
private或protected。 - 方法在同一类中被调用(直接调用,未通过代理)。
- 没有配置事务管理器。
- 抛出了非
RuntimeException异常。 - 事务方法被非 Spring 管理的对象外部调用。
- 事务的传播行为设置不当。
- 非事务方法调用事务方法时,事务失效。
- 只读事务进行数据修改。
避免失效的建议:
- 确保事务方法是
public。 - 使用 Spring 管理的代理对象来调用事务方法。
- 正确配置事务管理器。
- 根据需求调整
rollbackFor和propagation配置。 - 使用
readOnly标记仅进行查询的事务。
相关文章:
Spring 中的事务
🧾 一、什么是事务? 🧠 通俗理解: 事务 一组操作,要么全部成功,要么全部失败,不能只做一半。 比如你转账: A 账户扣钱B 账户加钱 如果 A 扣了钱但 B 没收到,那就出问…...
Java中的同步和异步
一、前言 在Java中,同步(Synchronous)和异步(Asynchronous)是两种不同的任务处理模式。核心区别在任务执行的顺序控制和线程阻塞行为。 二、同步(Synchronous) 定义:任务按顺序执行…...
vue2 vue3 响应式差异
vue2 响应式原理看这 链接: link 总结: object.defineproperty()是对属性的劫持,对属性劫持有两大缺陷 1. 需要遍历对象的所有属性,深层属性需递归,存在效率问题 2. 后添加的属性,无法获得响应式,因为劫持…...
唯一ID生成器设计方案
《亿级流量系统架构设计与实战》总结 1. 唯一ID的核心需求 • 全局唯一性:分布式系统中所有节点生成的ID不可重复。 • 趋势递增性(可选):ID按时间或序列递增,优化数据库写入性能。 • 高可用性:服务需72…...
OpenCV 图形API(16)将极坐标(magnitude 和 angle)转换为笛卡尔坐标(x 和 y)函数polarToCart()
操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 描述 计算二维向量的 x 和 y 坐标。 polarToCart 函数根据 magnitude 和 angle 的对应元素表示的每个二维向量,计算其笛卡尔坐标:…...
在 Ubuntu24.04 LTS 上 Docker Compose 部署基于 Dify 重构二开的开源项目 Dify-Plus
一、安装环境信息说明 硬件资源(GB 和 GiB 的主要区别在于它们的换算基数不同,GB 使用十进制,GiB 使用二进制,导致相同数值下 GiB 表示的容量略大于 GB;换算关系:1 GiB ≈ 1.07374 GB ;1 GB ≈ …...
安装和配置Docker
其他版本的安装方式可直接参考官方网站,推荐通过官方网站提供的方式安装Dockers,下面只是个演示的示例,仅供参考 Install | Docker Docs 安装 Docker 的前置准备 1.虚拟机配置: 推荐配置 内存:4GB(最低…...
Ansible YAML 基础语法与关键词 的详细指南
以下是 Ansible YAML 基础语法与关键词 的详细指南,帮助你快速掌握 Playbook 编写规范和核心概念: 目录 一、Ansible Playbook 基础结构1. YAML 文件基础 二、核心关键词1. Play 定义2. Task 定义3. Handler 定义4. 变量(Variables࿰…...
NO.64十六届蓝桥杯备战|基础算法-简单贪心|货仓选址|最大子段和|纪念品分组|排座椅|矩阵消除(C++)
贪⼼算法是两极分化很严重的算法。简单的问题会让你觉得理所应当,难⼀点的问题会让你怀疑⼈⽣ 什么是贪⼼算法? 贪⼼算法,或者说是贪⼼策略:企图⽤局部最优找出全局最优。 把解决问题的过程分成若⼲步;解决每⼀步时…...
瑞萨RA4M2使用心得-KEIL5的第一次编译
目录 前言 环境: 开发板:RA-Eco-RA4M2-100PIN-V1.0 IDE:keil5.35 一、软件的下载 编辑瑞萨的芯片,除了keil5 外还需要一个软件:RASC 路径:Releases renesas/fsp (github.com) 向下找到: …...
java根据集合中对象的属性值大小生成排名
1:根据对象属性降序排列 public static <T extends Comparable<? super T>> LinkedHashMap<T, Integer> calculateRanking(List<ProductPerformanceInfoVO> dataList, Function<ProductPerformanceInfoVO, T> keyExtractor) {Linked…...
数据分析-Excel-学习笔记
Day1 复现报表聚合函数:日期联动快速定位区域SUMIF函数SUMIFS函数环比、同比计算IFERROR函数混合引用单元格格式总结汇报 拿到一个Excel表格,首先要看这个表格个构成(包含了哪些数据),几行几列,每一列的名称…...
整车CAN网络和CANoe
车载网络中主要包含有Can网络,Lin网络,FlexRay,Most,以太网。 500kbps:500波特率,表示的数据传输的速度。表示的是最大的网速传输速度。也就是每秒 500kb BodyCan车身Can InfoCan娱乐信息Can 车身CAN主要连接的是ESB电动安全带 ADB自适应远光灯等 PTCan动力Can 底盘Can...
ChatGPT 的新图像生成器非常擅长伪造收据
本月,ChatGPT 推出了一种新的图像生成器,作为其 4o 模型的一部分,该模型在生成图像内的文本方面做得更好。 人们已经在利用它来生成假的餐厅收据,这可能会为欺诈者使用的已经很广泛的 AI 深度伪造工具包添加另一种工具。 多产的…...
JS页面尺寸事件
元素位置 在这里插入图片描述 父元素带有定位时输出相对于父亲元素的距离值...
SpringBoot的日志框架
目录 默认日志框架 日志配置 更换日志框架 排除默认Logback 引入目标日志框架 添加配置文件 logback.xml SpringBoot的核心设计宗旨是约定大于配置,很多框架功能都给你默认加载和配置完成供你使用,但这就要求使用者对框架有一定的理解和改造能力,比如这个日志框架,是其…...
网络协议之基础介绍
写在前面 本文看下网络协议相关基础内容。 1:为什么要有网络协议 为了实现世界各地的不同主机的互联互通。 2:协议的三要素 协议存在的目的就是立规矩,无规矩不成方圆嘛!但是这个规矩也不是想怎么立就怎么立的,也…...
【学Rust写CAD】34 精确 Alpha 混合函数(argb.rs补充方法)
源码 #[inline]pub fn over_exact(self, dst: Argb) -> Argb {let a 255 - self.alpha32();let t dst.rb() * a 0x80_00_80;let mut rb (t ((t >> 8) & Argb::MASK)) >> 8;rb & Argb::MASK;rb self.rb();// saturaterb | 0x1000100 - ((rb >&…...
利用NumPy核心知识点优化TensorFlow模型训练过程
利用NumPy核心知识点优化TensorFlow模型训练过程 NumPy是Python科学计算的基础库,掌握它的高效操作可以显著提升TensorFlow模型的训练效率。本文详细探讨如何将NumPy的核心优势应用于TensorFlow模型训练的各个环节。 1. 数据预处理优化 高效向量化操作 NumPy的向…...
初识数据结构——Java集合框架解析:List与ArrayList的完美结合
📚 Java集合框架解析:List与ArrayList的完美结合 🌟 前言:为什么我们需要List和ArrayList? 在日常开发中,我们经常需要处理一组数据。想象一下,如果你要管理一个班级的学生名单,或…...
TDengine 从入门到精通(2万字长文)
目录 第一章:走进 TDengine 的世界 TDengine 是个啥? TDengine 的硬核特性 性能炸裂 分布式架构,天生可扩展 SQL 用起来贼顺手 写入方式花样多 内置缓存,省心又省力 TDengine 能干啥? 智能制造 能源管理 物联网平台 工业大数据 第二章:上手 TDengine:安装与…...
DevOps 与持续集成(CI/CD)
1. DevOps 概述 DevOps(Development + Operations)是一种软件开发方法,强调开发(Dev)与运维(Ops)协作,通过自动化工具提高软件交付效率。其目标是: ✅ 提高部署速度 —— 频繁发布新版本 ✅ 减少人为错误 —— 通过自动化降低运维风险 ✅ 增强可观测性 —— 监控和日…...
[特殊字符] 使用 Handsontable 构建一个支持 Excel 公式计算的动态表格
在 Web 应用中,处理表格数据并提供 Excel 级的功能(如公式计算、数据导入导出)一直是个挑战。今天,我将带你使用 React Handsontable 搭建一个强大的 Excel 风格表格,支持 公式计算、Excel 文件导入导出,并…...
uniapp微信小程序引入vant组件库
1、首先要有uniapp项目,根据vant官方文档使用yarn或npm安装依赖: 1、 yarn init 或 npm init2、 # 通过 npm 安装npm i vant/weapp -S --production# 通过 yarn 安装yarn add vant/weapp --production# 安装 0.x 版本npm i vant-weapp -S --production …...
贪心进阶学习笔记
反悔贪心 贪心是指直接选择局部最优解,不需要考虑之后的影响。 而反悔贪心是在贪心上面加了一个“反悔”的操作,于是又可以撤销之前的“鲁莽行动”,让整个的选择稍微变得“理智一些”。 于是,我个人理解,反悔贪心是…...
Java 大视界 -- Java 大数据在航天遥测数据分析中的技术突破与应用(177)
💖亲爱的朋友们,热烈欢迎来到 青云交的博客!能与诸位在此相逢,我倍感荣幸。在这飞速更迭的时代,我们都渴望一方心灵净土,而 我的博客 正是这样温暖的所在。这里为你呈上趣味与实用兼具的知识,也…...
架构师面试(二十七):单链表
问题 今天的问题对于架构师来说会相对容易许多。今天出一个【数据结构与算法】相关的题目,醒醒脑。 给一张【单链表】,该单链表有100个节点元素(当然,事先我们是不知道100这个数目的),要获取倒数第8个元素…...
从扩展黎曼泽塔函数构造物质和时空的结构-15
回来考虑泽塔函数, 我们知道, 也就是在平面直角坐标系上反正切函数在x上的变化率,那么不难看出, 就是在s维空间上的“广义”反正切函数在单位p上的变化率,而泽塔函数,就是这些变化率的全乘积, 因…...
数据库访问工具 dbVisitor v6.0.0 发布
dbVisitor 是一款轻量小巧、功能完备的 Java 数据库 ORM 工具,它的前身是 HasorDB,历经 8 年迭代后正式更名为 dbVisitor 并开始独立发展4。以下是关于 dbVisitor v6.0.0 发布的相关信息: 发布说明 在 Maven Central 上可查询到 dbVisitor …...
01背包问题详解 具体样例模拟版
01背包 有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。 第 i 件物品的体积是 v i v_i vi,价值是 w i w_i wi。 求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。 输出最大价值。 输入格式 …...
