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事务的核心组件、传播行为的源码实现。下一篇中,我们将结合案例,来讲解实战中有关事务的易…...
JVM预热
阿里电商平台每年的各种大促活动,对于Java技术来说,其中重要一个操作环节就是预热操作。 目录 预热是什么?为什么要预热? java 程序不预热和预热的调用对比 预热是什么? 预热是指,在 JVM 启动后࿰…...
基于flask+vue框架的的医院预约挂号系统i1616(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
系统程序文件列表 项目功能:用户,医生,科室信息,就诊信息,医院概况,挂号信息,诊断信息,取消挂号 开题报告内容 基于FlaskVue框架的医院预约挂号系统开题报告 一、研究背景与意义 随着医疗技术的不断进步和人们健康意识的日益增强,医院就诊量逐年增加。传统的现场…...
DeepSeek掘金——SpringBoot 调用 DeepSeek API 快速实现应用开发
Spring Boot 实现 DeepSeek API 调用 1. 项目依赖 在 pom.xml 中添加以下依赖: <dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-webflux</artifactId></dependency>&l…...
easelog(1)基础C++日志功能实现
EaseLog(1)基础C日志功能实现 Author: Once Day Date: 2025年2月22日 一位热衷于Linux学习和开发的菜鸟,试图谱写一场冒险之旅,也许终点只是一场白日梦… 漫漫长路,有人对你微笑过嘛… 注:本简易日志组件代码实现参考了Google …...
epoll_event的概念和使用案例
epoll_event 是 Linux 下 epoll I/O 多路复用机制的核心数据结构,用于描述文件描述符(File Descriptor, FD)上发生的事件及其关联的用户数据。通过 epoll,可以高效地监控多个文件描述符的状态变化(如可读、可写、错误等…...
Leetcode2506:统计相似字符串对的数目
题目描述: 给你一个下标从 0 开始的字符串数组 words 。 如果两个字符串由相同的字符组成,则认为这两个字符串 相似 。 例如,"abca" 和 "cba" 相似,因为它们都由字符 a、b、c 组成。然而,"…...
蓝桥月赛 之 26场
文章目录 好汤圆灯笼猜谜元宵分配摆放汤圆 好汤圆 好汤圆 思路分析:由于2025能够被15整除,所以我们直接输出对应的答案即可 import os import sys# 请在此输入您的代码print(2025//15)灯笼猜谜 灯笼猜谜 思路分析:首先呢,我就考…...
机器学习面试八股文——决战金三银四
大家好,这里是好评笔记,公主 号:Goodnote,专栏文章私信限时Free。本笔记的任务是解读机器学习实践/面试过程中可能会用到的知识点,内容通俗易懂,入门、实习和校招轻松搞定。 公主号合集地址 点击进入优惠地…...
umi: valtio的使用
一、基本用法 import { proxy, useSnapshot } from umijs/max;// 1、定义数据 const state proxy({ count: 33 });export default () > {// 2、使用数据const snap useSnapshot(state);function increaseCount() {state.count 1;}return (<><h1>{snap.count}…...
区块链相关方法-波特五力分析模型
一、定义:波特五力分析模型(Porters Five Forces Framework)是迈克尔・波特(Michael Porter)于 1979 年提出的一种用于分析行业竞争态势的工具。它通过考察五种力量的相互作用来评估一个行业的吸引力和竞争环境,这五种…...
纷析云开源版- Vue2-增加字典存储到localStorage
main.js //保存字典数据到LocalStorage Vue.prototype.$api.setting.SystemDictType.all().then(({data}) > {loadDictsToLocalStorage(data) })新增 dictionary.js 放在 Utils文件夹里面 // 获取字典数据 export function getDictByType(dictType) {const dicts JSON.par…...
HTML项目一键打包工具:HTML2EXE 最新版
HTML2EXE 工具可以一键打包生成EXE可执行文件。可以打包任意HTML项目或者是一个网址为单个EXE文件,直接打开即可运行。支持KRPano全景VR项目、WebGL游戏项目、视频播放、,课件打包、网址打包等。 一、功能特点 类别序号功能标题1支持程序图标自定义(支持…...
Windows 中的启动项如何打开?管理电脑启动程序的三种方法
在日常使用电脑时,我们经常会发现一些应用程序在开机时自动启动,这不仅会拖慢系统的启动速度,还可能占用不必要的系统资源。幸运的是,通过几个简单的步骤,你可以轻松管理这些开机自启的应用程序。接下来,我…...
在 JavaScript 中接入 Facebook 事件
在 JavaScript 中接入 Facebook 事件 本文档介绍了如何在 JavaScript 中集成 Facebook Pixel 事件,用于跟踪网站的用户行为并提高广告效果。 1. 安装并初始化 Facebook Pixel 在开始接入事件之前,首先需要在你的网页中初始化 Facebook Pixel。Faceboo…...
如何在cursor上使用 deepseek 模型
引言 Cursor 虽提供免费试用,但试用时间有限,且后续使用可能会面临速度限制。不过,用户可以使用自己的 API key 来继续使用。值得一提的是,deepseek 模型使用成本极为低廉,能为使用者带来更多灵活性与经济性。基于此&…...
mysql的字符集和比较规则
mysql的字符集和比较规则 一、字符集(Character Set)二、比较规则(Collation)三、客户端与服务器的字符集转换四、注意事项总结 深度解读mysql是怎样运行的 MySQL的字符集和比较规则是其处理字符串存储、传输及比较的核心机制&…...
什么是LoRA微调
LoRA是大模型微调方法的一种,它的特点是只在模型的 部分权重(如 QKV 矩阵) 上 添加可训练参数 通过 低秩矩阵(AB) 来优化参数更新 优点: 极大降低显存消耗(deepseek 7B 只需 10GB) 适…...
热管理系统:新能源汽车的 “温度管家”
在新能源汽车的众多系统中,热管理系统堪称是一位默默守护的 “温度管家”,其重要性不容小觑。传统燃油车的热管理主要围绕发动机、变速箱冷却系统和空调系统,而新能源汽车的热管理则涵盖了电池系统、电机电控、空调系统等绝大部分零部件 &…...
如何修改Windows系统Ollama模型存储位置
默认情况下,Ollama 模型会存储在 C 盘用户目录下的 .ollama/models 文件夹中,这会占用大量 C 盘空间,增加C盘“爆红”的几率。所以,我们就需要修改Ollama的模型存储位置 Ollama提供了一个环境变量参数可以修改Ollama的默认存在位…...
调用支付宝接口响应40004 SYSTEM_ERROR问题排查
在对接支付宝API的时候,遇到了一些问题,记录一下排查过程。 Body:{"datadigital_fincloud_generalsaas_face_certify_initialize_response":{"msg":"Business Failed","code":"40004","sub_msg…...
C++八股 —— 单例模式
文章目录 1. 基本概念2. 设计要点3. 实现方式4. 详解懒汉模式 1. 基本概念 线程安全(Thread Safety) 线程安全是指在多线程环境下,某个函数、类或代码片段能够被多个线程同时调用时,仍能保证数据的一致性和逻辑的正确性…...
Xen Server服务器释放磁盘空间
disk.sh #!/bin/bashcd /run/sr-mount/e54f0646-ae11-0457-b64f-eba4673b824c # 全部虚拟机物理磁盘文件存储 a$(ls -l | awk {print $NF} | cut -d. -f1) # 使用中的虚拟机物理磁盘文件 b$(xe vm-disk-list --multiple | grep uuid | awk {print $NF})printf "%s\n"…...
Qemu arm操作系统开发环境
使用qemu虚拟arm硬件比较合适。 步骤如下: 安装qemu apt install qemu-system安装aarch64-none-elf-gcc 需要手动下载,下载地址:https://developer.arm.com/-/media/Files/downloads/gnu/13.2.rel1/binrel/arm-gnu-toolchain-13.2.rel1-x…...
k8s从入门到放弃之HPA控制器
k8s从入门到放弃之HPA控制器 Kubernetes中的Horizontal Pod Autoscaler (HPA)控制器是一种用于自动扩展部署、副本集或复制控制器中Pod数量的机制。它可以根据观察到的CPU利用率(或其他自定义指标)来调整这些对象的规模,从而帮助应用程序在负…...
实战设计模式之模板方法模式
概述 模板方法模式定义了一个操作中的算法骨架,并将某些步骤延迟到子类中实现。模板方法使得子类可以在不改变算法结构的前提下,重新定义算法中的某些步骤。简单来说,就是在一个方法中定义了要执行的步骤顺序或算法框架,但允许子类…...
Java多线程实现之Runnable接口深度解析
Java多线程实现之Runnable接口深度解析 一、Runnable接口概述1.1 接口定义1.2 与Thread类的关系1.3 使用Runnable接口的优势 二、Runnable接口的基本实现方式2.1 传统方式实现Runnable接口2.2 使用匿名内部类实现Runnable接口2.3 使用Lambda表达式实现Runnable接口 三、Runnabl…...
stm32进入Infinite_Loop原因(因为有系统中断函数未自定义实现)
这是系统中断服务程序的默认处理汇编函数,如果我们没有定义实现某个中断函数,那么当stm32产生了该中断时,就会默认跑这里来了,所以我们打开了什么中断,一定要记得实现对应的系统中断函数,否则会进来一直循环…...
AWS vs 阿里云:功能、服务与性能对比指南
在云计算领域,Amazon Web Services (AWS) 和阿里云 (Alibaba Cloud) 是全球领先的提供商,各自在功能范围、服务生态系统、性能表现和适用场景上具有独特优势。基于提供的引用[1]-[5],我将从功能、服务和性能三个方面进行结构化对比分析&#…...
性能优化中,多面体模型基本原理
1)多面体编译技术是一种基于多面体模型的程序分析和优化技术,它将程序 中的语句实例、访问关系、依赖关系和调度等信息映射到多维空间中的几何对 象,通过对这些几何对象进行几何操作和线性代数计算来进行程序的分析和优 化。 其中࿰…...
