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)负责实际的事务操作,包括开启事务、提交事务、回滚事务等。

🔍 四、事务处理的实际步骤

  1. 方法调用被代理对象拦截
    • @Transactional 标记的方法会被 AOP 代理拦截,调用 TransactionInterceptor
  2. 事务拦截器执行逻辑
    • TransactionInterceptor 会根据 @Transactional 配置,从容器中获取 事务管理器PlatformTransactionManager)。
  3. 判断事务传播行为
    • 如果当前没有事务(例如没有正在进行的事务),根据传播行为决定是否新建事务。默认是 REQUIRED,即如果没有事务,创建一个新的事务。
  4. 开启事务
    • 通过 PlatformTransactionManager 开启一个事务(实际是对底层数据库操作的封装)。
  5. 执行目标方法
    • 目标方法执行,在方法内的操作都在同一个事务中进行。
  6. 提交或回滚事务
    • 如果方法执行没有抛出异常,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. 方法是 privateprotected

Spring 的事务管理是基于 AOP(面向切面编程) 实现的,而 AOP 需要代理对象来执行方法。如果方法是 privateprotected,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.xmlSpring Boot 配置类中配置了正确的事务管理器。

Spring Boot 示例:

@Configuration
@EnableTransactionManagement
public class TransactionConfig {@Beanpublic PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {return new JpaTransactionManager(entityManagerFactory);}
}

4. 事务方法抛出了非 RuntimeException 异常

Spring 默认只对 运行时异常 (RuntimeException 和其子类) 回滚事务。如果你在方法中抛出了一个 检查型异常(checked exception(如 IOExceptionSQLException 等),默认情况下,Spring 不会回滚事务。

解决方法:

  • 使用 @TransactionalrollbackFor 属性来指定哪些异常需要回滚事务。
@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 事务失效的常见原因:

  1. 事务方法是 privateprotected
  2. 方法在同一类中被调用(直接调用,未通过代理)。
  3. 没有配置事务管理器
  4. 抛出了非 RuntimeException 异常
  5. 事务方法被非 Spring 管理的对象外部调用
  6. 事务的传播行为设置不当
  7. 非事务方法调用事务方法时,事务失效
  8. 只读事务进行数据修改

避免失效的建议:

  • 确保事务方法是 public
  • 使用 Spring 管理的代理对象来调用事务方法。
  • 正确配置事务管理器。
  • 根据需求调整 rollbackForpropagation 配置。
  • 使用 readOnly 标记仅进行查询的事务。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/75835.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Flutter极速接入IM聊天功能并支持鸿蒙

Flutter极速接入IM聊天功能并支持鸿蒙 如果你们也是Flutter项目&#xff0c;想快速接入聊天&#xff0c;包括聊天的UI界面&#xff0c;强烈推荐这一家。因为我们已经完成了集成&#xff0c;使用非常稳定&#xff0c;集成也非常快捷方便。 而且&#xff0c;就在今天&#xff0c…

C# 类库生成后自动复制到指定目录

C# 类库生成后自动复制到指定目录 在C#中,当你开发了一个类库项目(通常是.NET Core或.NET Framework项目),你可能会希望在构建(Build)完成后自动将生成的DLL文件复制到指定的目录。有几种方法可以实现这个需求,下面是一些常用的方法: 方法1:使用MSBuild的AfterBuild…

13-产品经理-产品多分支平台管理

禅道16.0版本开始&#xff0c;优化和增强了产品的分支/平台功能&#xff0c;主要特点如下&#xff1a; 多分支/平台功能兼容各种大小型项目&#xff0c;项目/迭代可以关联对应产品的某个分支/平台。分支/平台支持灵活管理&#xff0c;可以把分支/平台理解为时间层面的概念&…

手搓多模态-04 归一化介绍

在机器学习中&#xff0c;归一化是一个非常重要的工具&#xff0c;它能帮助我们加速训练的速度。在我们前面的SiglipVisionTransformer 中&#xff0c;也有用到归一化层&#xff0c;如下代码所示&#xff1a; class SiglipVisionTransformer(nn.Module): ##视觉模型的第二层&am…

Qt 入门 1 之第一个程序 Hello World

Qt 入门1之第一个程序 Hello World 直接上操作步骤从头开始认识&#xff0c;打开Qt Creator&#xff0c;创建一个新项目&#xff0c;并依次执行以下操作 在Qt Creator中&#xff0c;一个Kits 表示一个完整的构建环境&#xff0c;包括编译器、Qt版本、调试器等。在上图中可以直…

深入理解MySQL:核心特性、优化与实践指南

MySQL是一个开源的关系型数据库管理系统(RDBMS)&#xff0c;由瑞典MySQL AB公司开发&#xff0c;目前属于Oracle公司。它是目前世界上最流行的开源数据库之一&#xff0c;广泛应用于各种规模的Web应用和企业系统中。 目录 一、核心特点 关系型数据库&#xff1a; 开源免费&am…

Linux 系统安装与优化全攻略:打造高效开发环境

一、开篇引言 &#xff08;一&#xff09;Linux 系统的广泛应用 Linux 凭借其开源、稳定且安全的特性&#xff0c;在服务器、嵌入式设备以及开发环境等领域都有着极为广泛的应用。 &#xff08;二&#xff09;撰写本文的目的 为读者提供一套全面且实用的指南&#xff0c;助…

代码训练day22回溯算法p1

1.组合 &#xff08;1&#xff09;模板 void backtracking(参数) {if (终止条件) {存放结果;return;}for (选择&#xff1a;本层集合中元素&#xff08;树中节点孩子的数量就是集合的大小&#xff09;) {处理节点;backtracking(路径&#xff0c;选择列表); // 递归回溯&#…

2024华为OD机试真题-任务最优调度(C++/Java/Python)-E卷-200分

2024华为OD机试最新E卷题库-(D卷+E卷)-(JAVA、Python、C++) 目录 题目描述 输入描述 输出描述 用例1 考点 题目解析 代码 c++ java python 题目描述 给定一个正整数数组表示待系统执行的任务列表,数组的每一个元素代表一个任务,元素的值表示该任务的类型。请计算执…

每日习题:20250407

2025 2025 2025年 04 04 04月 06 06 06日 题目 1 设 X X X是实随机变量&#xff0c;任意光滑的函数 f : R → R f:\mathbf{R} \rightarrow \mathbf{R} f:R→R&#xff0c;都有&#xff1a; E ( X f ( X ) ) E ( f ′ ( X ) ) E\left(Xf(X)\right)E\left(f(X)\right) E(Xf(X)…

TensorRT 有什么特殊之处

一、TensorRT的定义与核心功能 TensorRT是NVIDIA推出的高性能深度学习推理优化器和运行时库&#xff0c;专注于将训练好的模型在GPU上实现低延迟、高吞吐量的部署。其主要功能包括&#xff1a; 模型优化&#xff1a;通过算子融合&#xff08;合并网络层&#xff09;、消除冗余…

JCR一区文章,壮丽细尾鹩莺算法Superb Fairy-wren Optimization-附Matlab免费代码

本文提出了一种新颖的基于群体智能的元启发式优化算法——壮丽细尾鹩优化算法&#xff08;SFOA&#xff09;,SFOA从精湛的神仙莺的生活习性中汲取灵感。融合了精湛的神仙莺群体中幼鸟的发育、繁殖后鸟类喂养幼鸟的行为以及它们躲避捕食者的策略。通过模拟幼鸟生长、繁殖和摄食阶…

使用Ubuntu18恢复群晖nas硬盘数据外接usb

使用Ubuntu18恢复群晖nas硬盘数据外接usb 1. 接入硬盘2.使用Ubuntu183.查看nas硬盘信息3. 挂载nas3.1 挂载损坏nas硬盘(USB)3.2 挂载当前运行的nas 4. 拷贝数据分批传输 5. 新旧数据对比 Synology NAS 出现故障&#xff0c;DS DiskStation损坏&#xff0c;则可以使用计算机和 U…

linux 安装 mysql记录

sudo apt-get install mysql-server 一直报错&#xff0c;按照下面的终于安装出来了 这个链接 https://cn.linux-console.net/?p13784 第 1 步&#xff1a;要删除 MySQL 及其所有依赖项&#xff0c;请执行以下命令&#xff1a; sudo apt-get remove --purge mysql* 第 2 步…

UE5学习笔记 FPS游戏制作35 使用.csv配置文件

文章目录 导入.csv要求首先创建一个结构体导入配置文件读取配置 导入 .csv要求 第一行必须包含标题 第一列的内容必须不能重复&#xff0c;因为第一列会被当成行的名字&#xff0c;在数据处理中发挥类似于字典的key的作用 当前的配置文件内容如下 首先创建一个结构体 结构…

谈谈策略模式,策略模式的适用场景是什么?

一、什么是策略模式&#xff1f;​​ 策略模式&#xff08;Strategy Pattern&#xff09;属于​​行为型设计模式​​。核心思路是将一组​​可替换的算法​​封装在独立的类中&#xff0c;使它们可以在运行时动态切换&#xff0c;同时使客户端代码与具体算法解耦。它包含三个…

AGI大模型(10):prompt逆向-巧借prompt

1 提示词逆向 明确逆向提示词⼯程概念 我们可以给ChatGPT提供⼀个简洁的提示词,让它能够更准确地理解我们所讨论的“逆向提示词⼯程”是什么意思,并通过这个思考过程,帮它将相关知识集中起来,进⽽构建⼀个专业的知识领域 提示词:请你举⼀个简单的例⼦,解释⼀下逆向pro…

IntelliJ IDEA全栈Git指南:从零构建到高效协作开发

文章目录 摘要 正文一、环境搭建&#xff1a;5分钟完成Git与IDEA的深度绑定二、基础操作&#xff1a;IDEA如何将Git效率提升300%三、分支管理&#xff1a;高并发团队的协作秘籍四、强制覆盖&#xff1a;高风险操作的生存指南五、实战避坑&#xff1a;血泪经验总结六、基于老项目…

指针的补充(用于学习笔记的记录)

1.指针基础知识 1.1 指针变量的定义和使用 指针也是一种数据类型&#xff0c;指针变量也是一种变量 指针变量指向谁&#xff0c;就把谁的地址赋值给指针变量 #include<stdio.h>int main() {int a 0;char b 100;printf("%p,%p \n", &a,&b); // …

【Cursor/VsCode】在文件列表中不显示.meta文件

打开设置文件 教程&#xff1a;【Cursor】打开Vscode设置 在settings.json里配置这个&#xff1a; "files.exclude": {"**/*.meta": true }