当前位置: 首页 > news >正文

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中。

大体流程为:

  1. 获取当前方法的@Transaction注解属性,创建TransactionDefinition对象;
  2. 获取TransactionManager对象;
  3. 根据方法名生成事务名;
  4. 如有必要则创建事务,并处理传播行为;
  5. 在try中执行下一个interceptor或被代理对象中方法;
  6. 异常时先回滚事务,正常时提交事务;
  7. 当前方法执行结束,还原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);}
}
  1. saveOrder()和updateInventory(),所有sql需要在同一个事务中;
  2. 单独调用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,逻辑如下:

  1. 创建DataSourceTransactionObject对象,从ThreadLocal中获取connectionHolder(可能为null);
  2. 当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,则开启事务

流程图如下:

三 总结

  1. Spring中,对于事务这一抽象概念,从多个方法进行了良好封装,如将隔离级别、超时时间等封装为TransactionDefinition,将事务状态、是否回滚等封装为TransactionStatus。
  2. 事务的传播行为,发生在方法间调用中。通过将connectionHolder放入ThreadLocal,实现了不同方法中使用同一数据库连接,从而支持多种传播方式。
  3. 事务底层,就是通过设置connection.autocommit为false,从而根据方法是否异常,选择commit还是rollback;
  4. 通过Savepoint实现嵌套事务(需要数据库支持)。
  5. 在执行某个方法时,判断当前是否已经存在事务,就是判断当前线程的ThreadLocal中是否存在一个数据库连接对象。

相关文章:

Spring事务原理 二

在上一篇博文《Spring事务原理 一》中&#xff0c;我们熟悉了Spring声明式事务的AOP原理&#xff0c;以及事务执行的大体流程。 本文中&#xff0c;介绍了Spring事务的核心组件、传播行为的源码实现。下一篇中&#xff0c;我们将结合案例&#xff0c;来讲解实战中有关事务的易…...

JVM预热

阿里电商平台每年的各种大促活动&#xff0c;对于Java技术来说&#xff0c;其中重要一个操作环节就是预热操作。 目录 预热是什么&#xff1f;为什么要预热&#xff1f; java 程序不预热和预热的调用对比 预热是什么&#xff1f; 预热是指&#xff0c;在 JVM 启动后&#xff0…...

基于flask+vue框架的的医院预约挂号系统i1616(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。

系统程序文件列表 项目功能:用户,医生,科室信息,就诊信息,医院概况,挂号信息,诊断信息,取消挂号 开题报告内容 基于FlaskVue框架的医院预约挂号系统开题报告 一、研究背景与意义 随着医疗技术的不断进步和人们健康意识的日益增强&#xff0c;医院就诊量逐年增加。传统的现场…...

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学习和开发的菜鸟&#xff0c;试图谱写一场冒险之旅&#xff0c;也许终点只是一场白日梦… 漫漫长路&#xff0c;有人对你微笑过嘛… 注&#xff1a;本简易日志组件代码实现参考了Google …...

epoll_event的概念和使用案例

epoll_event 是 Linux 下 epoll I/O 多路复用机制的核心数据结构&#xff0c;用于描述文件描述符&#xff08;File Descriptor, FD&#xff09;上发生的事件及其关联的用户数据。通过 epoll&#xff0c;可以高效地监控多个文件描述符的状态变化&#xff08;如可读、可写、错误等…...

Leetcode2506:统计相似字符串对的数目

题目描述&#xff1a; 给你一个下标从 0 开始的字符串数组 words 。 如果两个字符串由相同的字符组成&#xff0c;则认为这两个字符串 相似 。 例如&#xff0c;"abca" 和 "cba" 相似&#xff0c;因为它们都由字符 a、b、c 组成。然而&#xff0c;"…...

蓝桥月赛 之 26场

文章目录 好汤圆灯笼猜谜元宵分配摆放汤圆 好汤圆 好汤圆 思路分析&#xff1a;由于2025能够被15整除&#xff0c;所以我们直接输出对应的答案即可 import os import sys# 请在此输入您的代码print(2025//15)灯笼猜谜 灯笼猜谜 思路分析&#xff1a;首先呢&#xff0c;我就考…...

机器学习面试八股文——决战金三银四

大家好&#xff0c;这里是好评笔记&#xff0c;公主 号&#xff1a;Goodnote&#xff0c;专栏文章私信限时Free。本笔记的任务是解读机器学习实践/面试过程中可能会用到的知识点&#xff0c;内容通俗易懂&#xff0c;入门、实习和校招轻松搞定。 公主号合集地址 点击进入优惠地…...

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}…...

区块链相关方法-波特五力分析模型

一、定义:波特五力分析模型&#xff08;Porters Five Forces Framework&#xff09;是迈克尔・波特&#xff08;Michael Porter&#xff09;于 1979 年提出的一种用于分析行业竞争态势的工具。它通过考察五种力量的相互作用来评估一个行业的吸引力和竞争环境&#xff0c;这五种…...

纷析云开源版- 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文件&#xff0c;直接打开即可运行。支持KRPano全景VR项目、WebGL游戏项目、视频播放、,课件打包、网址打包等。 一、功能特点 类别序号功能标题1支持程序图标自定义&#xff08;支持…...

Windows 中的启动项如何打开?管理电脑启动程序的三种方法

在日常使用电脑时&#xff0c;我们经常会发现一些应用程序在开机时自动启动&#xff0c;这不仅会拖慢系统的启动速度&#xff0c;还可能占用不必要的系统资源。幸运的是&#xff0c;通过几个简单的步骤&#xff0c;你可以轻松管理这些开机自启的应用程序。接下来&#xff0c;我…...

在 JavaScript 中接入 Facebook 事件

在 JavaScript 中接入 Facebook 事件 本文档介绍了如何在 JavaScript 中集成 Facebook Pixel 事件&#xff0c;用于跟踪网站的用户行为并提高广告效果。 1. 安装并初始化 Facebook Pixel 在开始接入事件之前&#xff0c;首先需要在你的网页中初始化 Facebook Pixel。Faceboo…...

如何在cursor上使用 deepseek 模型

引言 Cursor 虽提供免费试用&#xff0c;但试用时间有限&#xff0c;且后续使用可能会面临速度限制。不过&#xff0c;用户可以使用自己的 API key 来继续使用。值得一提的是&#xff0c;deepseek 模型使用成本极为低廉&#xff0c;能为使用者带来更多灵活性与经济性。基于此&…...

mysql的字符集和比较规则

mysql的字符集和比较规则 一、字符集&#xff08;Character Set&#xff09;二、比较规则&#xff08;Collation&#xff09;三、客户端与服务器的字符集转换四、注意事项总结 深度解读mysql是怎样运行的 MySQL的字符集和比较规则是其处理字符串存储、传输及比较的核心机制&…...

什么是LoRA微调

LoRA是大模型微调方法的一种&#xff0c;它的特点是只在模型的 部分权重&#xff08;如 QKV 矩阵&#xff09; 上 添加可训练参数 通过 低秩矩阵&#xff08;AB&#xff09; 来优化参数更新 优点&#xff1a; 极大降低显存消耗&#xff08;deepseek 7B 只需 10GB&#xff09; 适…...

热管理系统:新能源汽车的 “温度管家”

在新能源汽车的众多系统中&#xff0c;热管理系统堪称是一位默默守护的 “温度管家”&#xff0c;其重要性不容小觑。传统燃油车的热管理主要围绕发动机、变速箱冷却系统和空调系统&#xff0c;而新能源汽车的热管理则涵盖了电池系统、电机电控、空调系统等绝大部分零部件 &…...

如何修改Windows系统Ollama模型存储位置

默认情况下&#xff0c;Ollama 模型会存储在 C 盘用户目录下的 .ollama/models 文件夹中&#xff0c;这会占用大量 C 盘空间&#xff0c;增加C盘“爆红”的几率。所以&#xff0c;我们就需要修改Ollama的模型存储位置 Ollama提供了一个环境变量参数可以修改Ollama的默认存在位…...

【根据当天日期输出明天的日期(需对闰年做判定)。】2022-5-15

缘由根据当天日期输出明天的日期(需对闰年做判定)。日期类型结构体如下&#xff1a; struct data{ int year; int month; int day;};-编程语言-CSDN问答 struct mdata{ int year; int month; int day; }mdata; int 天数(int year, int month) {switch (month){case 1: case 3:…...

IGP(Interior Gateway Protocol,内部网关协议)

IGP&#xff08;Interior Gateway Protocol&#xff0c;内部网关协议&#xff09; 是一种用于在一个自治系统&#xff08;AS&#xff09;内部传递路由信息的路由协议&#xff0c;主要用于在一个组织或机构的内部网络中决定数据包的最佳路径。与用于自治系统之间通信的 EGP&…...

django filter 统计数量 按属性去重

在Django中&#xff0c;如果你想要根据某个属性对查询集进行去重并统计数量&#xff0c;你可以使用values()方法配合annotate()方法来实现。这里有两种常见的方法来完成这个需求&#xff1a; 方法1&#xff1a;使用annotate()和Count 假设你有一个模型Item&#xff0c;并且你想…...

智能AI电话机器人系统的识别能力现状与发展水平

一、引言 随着人工智能技术的飞速发展&#xff0c;AI电话机器人系统已经从简单的自动应答工具演变为具备复杂交互能力的智能助手。这类系统结合了语音识别、自然语言处理、情感计算和机器学习等多项前沿技术&#xff0c;在客户服务、营销推广、信息查询等领域发挥着越来越重要…...

基于Java+MySQL实现(GUI)客户管理系统

客户资料管理系统的设计与实现 第一章 需求分析 1.1 需求总体介绍 本项目为了方便维护客户信息为了方便维护客户信息&#xff0c;对客户进行统一管理&#xff0c;可以把所有客户信息录入系统&#xff0c;进行维护和统计功能。可通过文件的方式保存相关录入数据&#xff0c;对…...

安全突围:重塑内生安全体系:齐向东在2025年BCS大会的演讲

文章目录 前言第一部分&#xff1a;体系力量是突围之钥第一重困境是体系思想落地不畅。第二重困境是大小体系融合瓶颈。第三重困境是“小体系”运营梗阻。 第二部分&#xff1a;体系矛盾是突围之障一是数据孤岛的障碍。二是投入不足的障碍。三是新旧兼容难的障碍。 第三部分&am…...

C#中的CLR属性、依赖属性与附加属性

CLR属性的主要特征 封装性&#xff1a; 隐藏字段的实现细节 提供对字段的受控访问 访问控制&#xff1a; 可单独设置get/set访问器的可见性 可创建只读或只写属性 计算属性&#xff1a; 可以在getter中执行计算逻辑 不需要直接对应一个字段 验证逻辑&#xff1a; 可以…...

C#学习第29天:表达式树(Expression Trees)

目录 什么是表达式树&#xff1f; 核心概念 1.表达式树的构建 2. 表达式树与Lambda表达式 3.解析和访问表达式树 4.动态条件查询 表达式树的优势 1.动态构建查询 2.LINQ 提供程序支持&#xff1a; 3.性能优化 4.元数据处理 5.代码转换和重写 适用场景 代码复杂性…...

云安全与网络安全:核心区别与协同作用解析

在数字化转型的浪潮中&#xff0c;云安全与网络安全作为信息安全的两大支柱&#xff0c;常被混淆但本质不同。本文将从概念、责任分工、技术手段、威胁类型等维度深入解析两者的差异&#xff0c;并探讨它们的协同作用。 一、核心区别 定义与范围 网络安全&#xff1a;聚焦于保…...

Qt的学习(二)

1. 创建Hello Word 两种方式&#xff0c;实现helloworld&#xff1a; 1.通过图形化的方式&#xff0c;在界面上创建出一个控件&#xff0c;显示helloworld 2.通过纯代码的方式&#xff0c;通过编写代码&#xff0c;在界面上创建控件&#xff0c; 显示hello world&#xff1b; …...