Spring事务原理 二
在上一篇博文《Spring事务原理 一》中,我们熟悉了Spring声明式事务的AOP原理,以及事务执行的大体流程。
本文中,介绍了Spring事务的核心组件、传播行为的源码实现。下一篇中,我们将结合案例,来讲解实战中有关事务的易错点。
本文中源码来自Spring 5.3.x分支,github源码地址:GitHub - spring-projects/spring-framework: Spring Framework
一 Spring事务的核心组件
了解相关类和接口,看看Spring对概念、术语是如何封装的?
1.1 PlatformTransactionManager
事务管理器接口,负责获取数据库连接,事务的开启、提交和回滚。

有抽象实现AbstractPlatformTransactionManager,其中定义了doGetTransaction、doBegin、doCommit、doRollback等抽象方法,待子类实现。
AbstractPlatformTransactionManager中有几个值得关注的属性:
// 是否允许嵌套事务
private boolean nestedTransactionAllowed = false;// 局部失败时全局回滚,当为false时,部分失败则不回滚
private boolean globalRollbackOnParticipationFailure = true;private boolean failEarlyOnGlobalRollbackOnly = false;private boolean rollbackOnCommitFailure = false;
它有以下常见子类:
DataSourceTransactionManager:用于JDBC和MyBatis等基于数据源的事务管理。HibernateTransactionManager:用于Hibernate框架的事务管理。JpaTransactionManager:用于JPA(Java Persistence API)的事务管理。
DataSourceTransactionManager
该类中定义了两个属性,对doCommit等抽象方法提供实现。

- doGetTransaction方法:创建一个DataSourceTransactionObject对象,设置connection。

- doBegin方法:执行事务前的准备工作,如设置
- 如果DataSourceTransactionObject没有连接,则获取一个连接
- 根据TransactionDefinition,为connection设置属性,如isolationLevel、readOnly、timeout;
- connection.setAutoCommit(false),关闭自动提交;
-

- 将connection与当前线程绑定;
-

- doCommit方法:从TransactionStatus中获取TransactionObject,拿到connection调用commit();

- doRollback方法:与doCommit实现相似,只是调connection.rollback();
1.2 TransactionDefinition
定义事务的属性,如隔离级别、传播行为、超时时间等。

隔离级别(Isolation Level):定义了事务之间的隔离程度,常见的有:
DEFAULT:使用数据库默认的隔离级别。READ_UNCOMMITTED:允许读取未提交的数据,可能导致脏读。READ_COMMITTED:只能读取已提交的数据,避免脏读。REPEATABLE_READ:确保在同一事务中多次读取同一数据时,结果一致。SERIALIZABLE:最高的隔离级别,确保事务串行执行,避免脏读、不可重复读和幻读。
超时时间(Timeout):事务的超时时间,超过该时间未完成则自动回滚。
只读(Read-only):指定事务是否为只读事务,优化性能。
在子类DefaultTransactionDefinition中,可以看到默认值:PROPAGATION_REQUIRED、ISOLATION_DEFAULT、TIMEOUT_DEFAULT、非readOnly。

当使用@Transactional时,会将注解属性解析成一个TransactionDefinition对象。
1.3 TransactionStatus
表示事务的状态,提供了以下方法:
isNewTransaction():判断当前事务是否为新事务。hasSavepoint():判断是否存在保存点(用于嵌套事务)。setRollbackOnly():标记事务为回滚状态。isRollbackOnly():判断事务是否被标记为回滚。
在子类DefaultTransactionStatus中,有这些属性
private boolean rollbackOnly = false;private boolean completed = false;private final Object transaction;private final boolean newTransaction;private final boolean newSynchronization;private final boolean readOnly;
1.4 TransactionSynchronizationManager
事务同步管理器,用于将事务相关信息与当前线程绑定,以支持各种事务传播行为。其中有多个ThreadLocal属性。

为什么保存连接的resources是Map类型?因为支持多数据源,当一个方法中操作多个数据库时,线程中就得保存多个connectionHolder对象,因此使用Map结构,key就是dataSource对象。
1.5 TransactionInterceptor
事务拦截器,就是AOP的代理逻辑,具体实现在TransactionAspectSupport#invokeWithinTransaction中。
大体流程为:
- 获取当前方法的@Transaction注解属性,创建TransactionDefinition对象;
- 获取TransactionManager对象;
- 根据方法名生成事务名;
- 如有必要则创建事务,并处理传播行为;
- 在try中执行下一个interceptor或被代理对象中方法;
- 异常时先回滚事务,正常时提交事务;
- 当前方法执行结束,还原TransactionInfo(恢复上层方法的事务信息)。

二 事务的传播机制
2.1 什么是事务传播
在日常开发中,业务代码中经常出现方法间调用,比如购物时下单减和库存:
import com.xiakexing.dao.InventoryDao;
import com.xiakexing.dao.OrderDao;
import com.xiakexing.entity.Order;public class OrderService {private OrderDao orderDao;private InventoryDao inventoryDao;public void saveOrder(Order order) {orderDao.save(order);updateInventory(order.getCode(), order.getCount());}public void updateInventory(String code, int count) {inventoryDao.update(code, count);}
}
- saveOrder()和updateInventory(),所有sql需要在同一个事务中;
- 单独调用updateInventory()时,如进货时不需要事务。
可见,updateInventory方法,在不同场景下对事务有不同要求。Spring中又如何实现呢?
Spring定义了传播行为(Propagation Behavior),定义了方法间调用时事务如何传递,类型有:
REQUIRED:如果当前线程存在事务,则加入该事务;如果不存在,则创建一个新事务。REQUIRES_NEW:总是创建一个新事务,如果当前线程存在事务,则挂起当前事务。SUPPORTS:如果当前线程存在事务,则加入该事务;如果不存在,则以非事务方式执行。NOT_SUPPORTED:以非事务方式执行,如果当前线程存在事务,则挂起当前事务。MANDATORY:如果当前线程存在事务,则加入该事务;如果不存在则抛出异常。NEVER:以非事务方式执行,如果当前线程存在事务,则抛出异常。NESTED:如果当前线程存在事务,则以嵌套事务中执行;如果不存在,则创建一个新事务。
这儿为什么强调线程呢?
方法间调用都在某个线程的方法栈中,按FILO顺序执行。如果两个方法的中sql使用两个不同的数据库连接执行,显然无法纳入一个事务中。
因为,数据库连接必须能够跨方法传递,Spring底层就是将connection放到ThreadLocal中。
2.2 传播机制的实现
2.2.1 线程绑定连接
在DataSourceTransactionManager#doBegin中:
- 从DataSource获取数据连接connection,设置autocommit=false、隔离级别、超时时间等属性;
- 将connection放入ThreadLocal<Map>,Map的key是DataSource对象,value是connectionHolder对象。


可见,方法间调用时可以从ThreadLocal中拿到同一个连接,去执行不同的SQL,进而一同提交或回滚。
2.2.2 处理传播机制
真正执行被代理对象方法前,会判断是否创建事务。


调用AbstractPlatformTransactionManager#getTransaction,逻辑如下:
- 创建DataSourceTransactionObject对象,从ThreadLocal中获取connectionHolder(可能为null);
- 当connectionHolder不为null且connectionHolder.transactionActive=ture时,说明已存在事务:

如果当前线程中存在事务:
- 如果当前方法传播行为是PROPAGATION_NEVER,则抛异常
-

- 如果是PROPAGATION_NOT_SUPPORTED,则挂起当前事务,用一个新连接的非事务方式执行当前方法;
-

- 如果是PROPAGATION_REQUIRES_NEW,则挂起当前事务,开启一个新事务(获取新连接并带事务执行);
-

- 如果是PROPAGATION_NESTED,则先设置savepoints(可以回滚到此处),然后使用同一个连接继续执行。
-

如果当前线程中不存在事务:
- 如果传播行为是PROPAGATION_MANDATORY,则抛异常
-

- 如果传播行为是PROPAGATION_REQUIRED、PROPAGATION_REQUIRES_NEW、PROPAGATION_NESTED,则开启事务
-

流程图如下:

三 总结
- Spring中,对于事务这一抽象概念,从多个方法进行了良好封装,如将隔离级别、超时时间等封装为TransactionDefinition,将事务状态、是否回滚等封装为TransactionStatus。
- 事务的传播行为,发生在方法间调用中。通过将connectionHolder放入ThreadLocal,实现了不同方法中使用同一数据库连接,从而支持多种传播方式。
- 事务底层,就是通过设置connection.autocommit为false,从而根据方法是否异常,选择commit还是rollback;
- 通过Savepoint实现嵌套事务(需要数据库支持)。
- 在执行某个方法时,判断当前是否已经存在事务,就是判断当前线程的ThreadLocal中是否存在一个数据库连接对象。
相关文章:
Spring事务原理 二
在上一篇博文《Spring事务原理 一》中,我们熟悉了Spring声明式事务的AOP原理,以及事务执行的大体流程。 本文中,介绍了Spring事务的核心组件、传播行为的源码实现。下一篇中,我们将结合案例,来讲解实战中有关事务的易…...
SpringAI系列 - ToolCalling篇(二) - 如何设置应用侧工具参数ToolContext(有坑)
目录 一、引言二、集成ToolContext示例步骤1: 在`@Tool`标注的工具方法中集成`ToolConext`参数步骤2:`ChatClient`运行时动态设置`ToolContext`参数三、填坑一、引言 在使用AI大模型的工具调用机制时,工具参数都是由大模型解析用户输入上下文获取的,由大模型提供参数给本地…...
本地部署MindSearch(开源 AI 搜索引擎框架),然后上传到 hugging face的Spaces——L2G6
部署MindSearch到 hugging face Spaces上——L2G6 任务1 在 官方的MindSearch页面 复制Spaces应用到自己的Spaces下,Space 名称中需要包含 MindSearch 关键词,请在必要的步骤以及成功的对话测试结果当中 实现过程如下: 2.1 MindSearch 简…...
MyBatis Plus扩展功能
一、代码生成器 二、逻辑删除 三、枚举处理器 像状态字段我们一般会定义一个枚举,做业务判断的时候就可以直接基于枚举做比较。但是我们数据库采用的是int类型,对应的PO也是Integer。因此业务操作时必须手动把枚举与Integer转换,非常麻烦。 …...
深度学习之自然语言处理CBOW预测及模型的保存
自然语言处理CBOW预测及模型的保存 目录 自然语言处理CBOW预测及模型的保存1 自然语言处理1.1 概念1.2 词向量1.2.1 one-hot编码1.2.2 词嵌入1.2.3 常见的词嵌入模型 2 CBOW预测模型搭建2.1 数据及模型确定2.1.1 数据2.1.2 CBOW模型2.1.3 词嵌入降维 2.2 数据预处理2.3 模型搭建…...
qt项目配置部署
Test项目: 子项目testFileHelper 1.新建一个test项目的子项目:取名testFileHelper 2.编写测试用例 3.pro文件中引入qosbrowser 4.引入测试对象的cpp和头文件 2.在项目中引入资源文件testfile.txt,在其中输入abc 实现thrid目录复用 移动thrid 将thrild目录统一放在章…...
java方法学习
java 方法 在Java中,方法是类(或对象)的行为或功能的实现。(一起实现一个功能)java的方法类似于其他语言的函数,是一段用来完成特定功能的代码片段。 方法是解决一类问题步骤的有序结合。 方法包含于类或…...
基于vue和微信小程序的校园自助打印系统(springboot论文源码调试讲解)
第3章 系统设计 3.1系统功能结构设计 本系统的结构分为管理员和用户、店长。本系统的功能结构图如下图3.1所示: 图3.1系统功能结构图 3.2数据库设计 本系统为小程序类的预约平台,所以对信息的安全和稳定要求非常高。为了解决本问题,采用前端…...
解析CV/多模态算法的要点及技术特点,弥补单模态信息不足的多模态应用的哪些场景中?
CV(计算机视觉)多模态算法是计算机科学领域的重要研究方向,融合了多种模态的数据来提升对视觉信息的理解和处理能力。 以下是一个结合自动驾驶行业的多模态大模型算法示例,采用特征级融合策略,结合摄像头图像和激光雷…...
[漏洞篇]文件上传漏洞详解
[漏洞篇]文件上传漏洞详解 一、介绍 1. 概念 文件上传漏洞是指用户上传了一个可执行的脚本文件,并通过此脚本文件获得了执行服务器端命令的能力。这种攻击方式是最为直接和有效的,“文件上传” 本身没有问题,有问题的是文件上传后…...
11.Docker 之分布式仓库 Harbor
Docker 之分布式仓库 Harbor Docker 之分布式仓库 Harbor1. Harbor 组成2. 安装 Harbor Docker 之分布式仓库 Harbor Harbor 是一个用于存储和分发 Docker 镜像的企业级 Registry 服务器,由 VMware 开源,其通过添加一些企业必需的功能特性,例…...
Python项目源码34:网页内容提取工具1.0(Tkinter+requests+html2text)
------★Python练手项目源码★------- Python项目32:订单销售额管理系统1.0(TkinterCSV) Python项目31:初学者也能看懂的聊天机器人1.0源码(命令行界面Re正则表达式) Python项目源码30:待办事…...
使用Termux将安卓手机变成随身AI服务器(page assist连接)
通过以下方法在安卓手机上运行 Ollama 及大模型,无需 Root 权限,具体方案如下: 通过 Termux 模拟 Linux 环境运行 核心工具: 安装 (安卓终端模拟器)()]。借助 proot-distro 工具安装 Linux 发行版…...
SpringBoot3中跨域问题解决
问题 SpringBoot3 中处理跨域请求 异常 浏览器在 localhost:3000 地址请求后端 http://127.0.0.1:8080 时, 报错提示 CORS 问题. 默认使用 Get 请求正常, 其他会提示. 使用 SpringBoot 3.4.2 版本配合 SpringSecurity 配置 Access to fetch at http://127.0.0.1:8080/todo-…...
kotlin Java 使用ArrayList.add() ,set()前面所有值被 覆盖 的问题
一、问题描述和分析 结构体的字段的属性为静态, 意味着该类的所有对象共享同一个属性,所以尽管集合里存的是不同的对象,但是对象的属性还是同一个值 修改其中任何一个,其他的也会“被修改”。使用kotlin语言时候,经常…...
力扣-回溯-51 N皇后
思路 在棋盘上放皇后在回溯方法的树上来说,深度就是每一行放的皇后,宽度就是for循环里遍历放皇后,还有个问题是需要判断当前位置是否允许放皇后 代码 class Solution { public:vector< vector<string> > result;vector<str…...
flink-cdc同步数据到doris中
1 创建数据库和表 1.1 数据库脚本 这样直接创建数据库是有问题,因为后面发现superset连接使用doris://root:12345610.101.12.82:9030/internal.eayc?charsetutf8mb4 -- 创建数据库eayc create database if not exists ods_eayc; -- 创建数据表2 数据同步 2.1 f…...
Git命令行入门
诸神缄默不语-个人CSDN博文目录 之前写过一篇VSCode Git的博文:VSCode上的Git使用手记(持续更新ing…) 现在随着开发经历增加,感觉用到命令行之类复杂功能的机会越来越多了,所以我专门再写一篇Git命令行的文章。 G…...
DeepSeek R1/V3满血版——在线体验与API调用
前言:在人工智能的大模型发展进程中,每一次新模型的亮相都宛如一颗投入湖面的石子,激起层层波澜。如今,DeepSeek R1/V3 满血版强势登场,为大模型应用领域带来了全新的活力与变革。 本文不但介绍在线体验 DeepSeek R1/…...
关于 BK3633 上电时受串口 UART2 影响而无法启动的问题说明
1. 问题描述 BK3633 SDK 版本:BK3633_DesignKit_V06_2310 使用 BK3633 UART2 与指纹模块进行通讯,为了降低功耗,通过 GPIO 控制了指纹模块的供电电源。但每次给整个系统板子上电时,BK3633 很大概率会实际而无法正常运行程序&…...
Redis7——基础篇(六)
前言:此篇文章系本人学习过程中记录下来的笔记,里面难免会有不少欠缺的地方,诚心期待大家多多给予指教。 基础篇: Redis(一)Redis(二)Redis(三)Redis&#x…...
简单工厂模式 (Simple Factory Pattern) 在Spring Boot 中的应用
简单工厂模式(Simple Factory Pattern)虽然不属于 GoF 23 种经典设计模式,但在实际开发中非常常用,尤其是在 Spring Boot 项目中。它提供了一种简单的方式来创建对象,将对象的创建逻辑集中到一个工厂类中。 一、简单工…...
Python简单使用MinerU
Python简单使用MinerU 1 简介 MinerU是国产的一款将PDF转化为机器可读格式的工具(如markdown、json),可以很方便地抽取为任意格式。目前支持图像(.jpg及.png)、PDF、Word(.doc及.docx)、以及P…...
使用AI创建流程图和图表的 3 种简单方法
你可能已经尝试过使用 LLMs 生成图像,但你有没有想过用它们来创建 流程图和图表?这些可视化工具对于展示流程、工作流和系统架构至关重要。 通常,在在线工具上手动绘制图表可能会耗费大量时间。但你知道吗?你可以使用 LLMs 通过简…...
ImportError: cannot import name ‘FixtureDef‘ from ‘pytest‘
错误信息表明 pytest 在尝试导入 FixtureDef 时出现了问题。通常是由于 pytest 版本不兼容 或 插件版本冲突 引起的。以下是详细的排查步骤和解决方案: 1. 检查 pytest 版本 首先,确认当前安装的 pytest 版本。某些插件可能需要特定版本的 pytest 才能…...
机器学习实战(7):聚类算法——发现数据中的隐藏模式
第7集:聚类算法——发现数据中的隐藏模式 在机器学习中,聚类(Clustering) 是一种无监督学习方法,用于发现数据中的隐藏模式或分组。与分类任务不同,聚类不需要标签,而是根据数据的相似性将其划…...
z-score算法
z-score算法原理参考网址 https://blog.csdn.net/m0_59596937/article/details/128378641 具体实现代码如下: import numpy as npclass ZScoreOutlierDetector:def __init__(self, threshold3):"""构造函数"""self.threshold thre…...
企业级RAG开源项目分享:Quivr、MaxKB、Dify、FastGPT、RagFlow
企业级 RAG GitHub 开源项目深度分享:Quivr、MaxKB、Dify、FastGPT、RagFlow 及私有化 LLM 部署建议 随着生成式 AI 技术的成熟,检索增强生成(RAG)已成为企业构建智能应用的关键技术。RAG 技术能够有效地将大型语言模型ÿ…...
open webui 部署 以及解决,首屏加载缓慢,nginx反向代理访问404,WebSocket后端服务器链接失败等问题
项目地址:GitHub - open-webui/open-webui: User-friendly AI Interface (Supports Ollama, OpenAI API, ...) 选择了docker部署 如果 Ollama 在您的计算机上,请使用以下命令 docker run -d -p 3000:8080 --add-hosthost.docker.internal:host-gatewa…...
C++ 智能指针 unique_ptr shared_ptr weak_ptr小练习
智能指针是 C11 引入的一项重要特性,它可以帮助我们管理动态分配的内存,自动释放内存,避免内存泄漏和悬空指针的问题。智能指针有三种常用类型:std::unique_ptr、std::shared_ptr 和 std::weak_ptr。 为了帮助你熟悉智能指针的使…...
