Spring 事务原理总结七

今天是二零二四年二月十八,农历正月初九。同时今天也是农历新年假期后的第一个工作日。我的内心既兴奋,又担忧,更急躁。兴奋是因为假期后的第一个工作日工作轻松;担忧是因为经过了这么长时间,我依旧没搞明白Spring事务,总觉得有些东西没搞懂,却又不知道是哪里,所以只能在这里徘徊不前;急躁是因为许多事情都要用钱去解决,自己那微薄的收入根本就是杯水车薪,而自己又不知道用哪种方式去筹集足够的资金来解决问题,所以只能整日急不可耐,而问题依旧。到底该怎么办呢?∙∙∙∙∙∙

针对第二种心理,我觉得没有其他办法,只能继续梳理,但也不能因此而放弃后面知识点的梳理。今天就让我们来聊一下Spring事务的几个面试题吧!我想围绕下面这样几个问题展开:

  1. 《Spring事务原理总结四》这篇文章中提到的将TransactionInfo绑定到当前线程的意义是什么呢?
  2. Spring是如何解决事务嵌套的?

首先回忆一下前面几篇文章的主要内容:《Spring事务原理总结一》主要梳理了事务的基本概念及特征,同时也梳理了Spring事务的基本用法;《Spring事务原理总结二》则主要梳理了Spring框架解析及注册事务代理的流程;《Spring事务原理总结三》则主要梳理了Spring事务的一些核心组件及其继承结构;《Spring事务原理总结四》梳理了Spring事务的执行流程;《Spring事务原理总结五》主要梳理并回答了前一篇文章中遗留的几个问题;《Spring事务原理总结六》主要梳理了Spring事务的异常回滚流程。接着让我们试着利用这些文章梳理的内容来回答这两个问题。

首先看第一个问题,将TransactionInfo对象绑定到当前线程的操作,在《Spring事务原理总结四》这篇文章中有提到,如下图所示:

当时只是说了这些代码位于TransactionAspectSupport类中,不过并没有深究其意义。现在让我们细化一下这个说法,然后在梳理完第二个问题后来回答这个问题。TransactionInfos是TransactionAspectSupport类中的最终静态内部类该类内部定义了bindToThread()方法和restoreThreadLocalStatus()方法。所以截图中说前者位于TransactionAspectSupport类的说法也是对的,因为其所在的类位于TransactionAspectSupport类中),其源码如下所示:

protected static final class TransactionInfo {@Nullableprivate final PlatformTransactionManager transactionManager;@Nullableprivate final TransactionAttribute transactionAttribute;private final String joinpointIdentification;@Nullableprivate TransactionStatus transactionStatus;@Nullableprivate TransactionInfo oldTransactionInfo;public TransactionInfo(@Nullable PlatformTransactionManager transactionManager,@Nullable TransactionAttribute transactionAttribute, String joinpointIdentification) {this.transactionManager = transactionManager;this.transactionAttribute = transactionAttribute;this.joinpointIdentification = joinpointIdentification;}public PlatformTransactionManager getTransactionManager() {Assert.state(this.transactionManager != null, "No PlatformTransactionManager set");return this.transactionManager;}@Nullablepublic TransactionAttribute getTransactionAttribute() {return this.transactionAttribute;}/*** Return a String representation of this joinpoint (usually a Method call)* for use in logging.*/public String getJoinpointIdentification() {return this.joinpointIdentification;}public void newTransactionStatus(@Nullable TransactionStatus status) {this.transactionStatus = status;}@Nullablepublic TransactionStatus getTransactionStatus() {return this.transactionStatus;}/*** Return whether a transaction was created by this aspect,* or whether we just have a placeholder to keep ThreadLocal stack integrity.*/public boolean hasTransaction() {return (this.transactionStatus != null);}private void bindToThread() {// Expose current TransactionStatus, preserving any existing TransactionStatus// for restoration after this transaction is complete.this.oldTransactionInfo = transactionInfoHolder.get();transactionInfoHolder.set(this);}private void restoreThreadLocalStatus() {// Use stack to restore old transaction TransactionInfo.// Will be null if none was set.transactionInfoHolder.set(this.oldTransactionInfo);}@Overridepublic String toString() {return (this.transactionAttribute != null ? this.transactionAttribute.toString() : "No transaction");}
}

这个类上有bindToThread()方法和restoreThreadLocalStatus()两个方法,它们的主要作用就是更改当前线程持有的ThreadLocal上的TransactionInfo值。

下面让我们来看一下第二个问题,要回答这个问题,需要从TransactionInterceptor的invoke(MethodInvocation)方法看起,这个方法的代码如下所示:

@Nullable
protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,final InvocationCallback invocation) throws Throwable {// If the transaction attribute is null, the method is non-transactional.TransactionAttributeSource tas = getTransactionAttributeSource();final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);final TransactionManager tm = determineTransactionManager(txAttr);// ……
// 这里删除了一些无用代码PlatformTransactionManager ptm = asPlatformTransactionManager(tm);final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager cpptm)) {// Standard transaction demarcation with getTransaction and commit/rollback calls.TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);Object retVal;try {// This is an around advice: Invoke the next interceptor in the chain.// This will normally result in a target object being invoked.retVal = invocation.proceedWithInvocation();}catch (Throwable ex) {// target invocation exceptioncompleteTransactionAfterThrowing(txInfo, ex);throw ex;}finally {cleanupTransactionInfo(txInfo);}if (retVal != null && txAttr != null) {TransactionStatus status = txInfo.getTransactionStatus();if (status != null) {if (retVal instanceof Future<?> future && future.isDone()) {try {future.get();}catch (ExecutionException ex) {if (txAttr.rollbackOn(ex.getCause())) {status.setRollbackOnly();}}catch (InterruptedException ex) {Thread.currentThread().interrupt();}}else if (vavrPresent && VavrDelegate.isVavrTry(retVal)) {// Set rollback-only in case of Vavr failure matching our rollback rules...retVal = VavrDelegate.evaluateTryFailure(retVal, txAttr, status);}}}commitTransactionAfterReturning(txInfo);return retVal;}else {Object result;final ThrowableHolder throwableHolder = new ThrowableHolder();// It's a CallbackPreferringPlatformTransactionManager: pass a TransactionCallback in.try {result = cpptm.execute(txAttr, status -> {TransactionInfo txInfo = prepareTransactionInfo(ptm, txAttr, joinpointIdentification, status);try {Object retVal = invocation.proceedWithInvocation();if (retVal != null && vavrPresent && VavrDelegate.isVavrTry(retVal)) {// Set rollback-only in case of Vavr failure matching our rollback rules...retVal = VavrDelegate.evaluateTryFailure(retVal, txAttr, status);}return retVal;}catch (Throwable ex) {if (txAttr.rollbackOn(ex)) {// A RuntimeException: will lead to a rollback.if (ex instanceof RuntimeException runtimeException) {throw runtimeException;}else {throw new ThrowableHolderException(ex);}}else {// A normal return value: will lead to a commit.throwableHolder.throwable = ex;return null;}}finally {cleanupTransactionInfo(txInfo);}});}catch (ThrowableHolderException ex) {throw ex.getCause();}catch (TransactionSystemException ex2) {if (throwableHolder.throwable != null) {logger.error("Application exception overridden by commit exception", throwableHolder.throwable);ex2.initApplicationException(throwableHolder.throwable);}throw ex2;}catch (Throwable ex2) {if (throwableHolder.throwable != null) {logger.error("Application exception overridden by commit exception", throwableHolder.throwable);}throw ex2;}// Check result state: It might indicate a Throwable to rethrow.if (throwableHolder.throwable != null) {throw throwableHolder.throwable;}return result;}
}

当调用事务代理对象时,程序会经过判断后,走进if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager cpptm))分支。首先让我们考虑一下非事务嵌套的操作场景,if分支中的retVal = invocation.proceedWithInvocation();就表示业务代码执行完成了,后面就是释放资源(即调用cleanupTransactionInfo(txInfo)方法)、提交事务(则是指调用commitTransactionAfterReturning(txInfo)方法)等一系列常规操作。这样整个事务和业务处理逻辑就执行完成了。接着再来考虑一下事务嵌套的处理场景(即需要事务的业务处理代码中调用了另外一个需要事务的业务代码),这个时候第一次进入if分支的程序执行完retVal = invocation.proceedWithInvocation();后,会再次进入到if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager cpptm))分支,这个时候我们需要关注if分支中的TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification)这句,先来看一下这个被调用的方法(createTransactionIfNecessary())及其关联方法(prepareTransactionInfo())的源码:

protected TransactionInfo createTransactionIfNecessary(@Nullable PlatformTransactionManager tm,@Nullable TransactionAttribute txAttr, final String joinpointIdentification) {// If no name specified, apply method identification as transaction name.if (txAttr != null && txAttr.getName() == null) {txAttr = new DelegatingTransactionAttribute(txAttr) {@Overridepublic String getName() {return joinpointIdentification;}};}TransactionStatus status = null;if (txAttr != null) {if (tm != null) {status = tm.getTransaction(txAttr);}else {if (logger.isDebugEnabled()) {logger.debug("Skipping transactional joinpoint [" + joinpointIdentification +"] because no transaction manager has been configured");}}}return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
}
// 
protected TransactionInfo prepareTransactionInfo(@Nullable PlatformTransactionManager tm,@Nullable TransactionAttribute txAttr, String joinpointIdentification,@Nullable TransactionStatus status) {TransactionInfo txInfo = new TransactionInfo(tm, txAttr, joinpointIdentification);if (txAttr != null) {// We need a transaction for this method...if (logger.isTraceEnabled()) {logger.trace("Getting transaction for [" + txInfo.getJoinpointIdentification() + "]");}// The transaction manager will flag an error if an incompatible tx already exists.txInfo.newTransactionStatus(status);}else {// The TransactionInfo.hasTransaction() method will return false. We created it only// to preserve the integrity of the ThreadLocal stack maintained in this class.if (logger.isTraceEnabled()) {logger.trace("No need to create transaction for [" + joinpointIdentification +"]: This method is not transactional.");}}// We always bind the TransactionInfo to the thread, even if we didn't create// a new transaction here. This guarantees that the TransactionInfo stack// will be managed correctly even if no transaction was created by this aspect.txInfo.bindToThread();return txInfo;
}

这时我们主要关注prepareTransactionInfo()方法中的txInfo.bindToThread(),关于这个被调用的方法的详情可以看参看前面的源码。这里再贴一下图片:

从图中不难看出,这个方法会先从当前线程的ThreadLocal中拿出一个TransactionInfo对象,并将其赋值给TransactionInfo的oldTransactionInfo属性,然后将新的TransactionInfo对象重新赋值到当前线程的ThreadLocal中。接着就是就是继续调用retVal = invocation.proceedWithInvocation(),然后调用cleanupTransactionInfo(txInfo)方法,最后再调用commitTransactionAfterReturning(txInfo)方法。这里我们再啰嗦一下cleanupTransactionInfo ()方法,其源码如下图所示:

protected void cleanupTransactionInfo(@Nullable TransactionInfo txInfo) {if (txInfo != null) {txInfo.restoreThreadLocalStatus();}
}

该方法最终调用的是TransactionInfo类中的restoreThreadLocalStatus()方法,其源码如下图所示:

说白了就是将当前TransactionInfo对象中的oldTransactionInfo对象重新赋值到当前线程的ThreadLocal对象中,这个对象就是嵌套事务的上一层事务。至此我们就可以用自己的语言来回答前面的两个问题了!

总的来看,将TransactionInfo绑定到当前线程的主要目的就是解决嵌套事务的。Spring解决嵌套的主要思路就是先将当前的TransactionInfo对象绑定到当前线程,当当前TransactionInfo对应的业务处理代码调用其他事务代理时,会将当前线程中保存的TransactionInfo对象赋值给新的TransactionInfo对象的oldTransactionInfo属性,然后将新的TransactionInfo对象重新绑定到当前线程的ThreadLocal上,这样就实现了前后两个事务的关联,并完成对事务传播行为的处理

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

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

相关文章

【论文解读】Latency-Aware Collaborative Perception

Latency-Aware Collaborative Perception 摘要引言方法SystemSyncNet 实验 摘要 协作感知最近显示出提高单智能体感知感知能力的巨大潜力。现有的协同感知方法通常考虑理想的通信环境。然而&#xff0c;在实践中&#xff0c;通信系统不可避免地存在延迟问题&#xff0c;导致安…

人工智能技术应用笔记(一):SORA已来,AI将开启新纪元

目录 01. OpenAI王炸级产品视频生成模型Sora发布 02. Sora发布的潜在影响 ▎C端 / 对于普通人 ▎B端 / 对于商业公司 03. 该如何看待Sora&#xff1f; 1.拥抱变化&#xff0c;让自己成为身边最懂AI的人 2.想象自己是一家一人公司的创始人 3.保持好奇心&…

抓包分析 TCP 协议

TCP 协议是在传输层中&#xff0c;一种面向连接的、可靠的、基于字节流的传输层通信协议。 环境准备 对接口测试工具进行分类&#xff0c;可以如下几类&#xff1a; 网络嗅探工具&#xff1a;tcpdump&#xff0c;wireshark 代理工具&#xff1a;fiddler&#xff0c;charles&…

JavaScript:隐式类型转换与显式类型转换

文章目录 隐式类型转换&#xff08;Implicit Type Conversion&#xff09;1、字符串与数字的转换2、非布尔值到布尔值的转换3、在相等性比较中的转换4、对象到基础类型的转换5、在算术运算符中的其他转换 显式类型转换&#xff08;Explicit Type Conversion&#xff09;1、Numb…

学员回访 | 天爷啊!我找到月薪16K的工作了!

终于轮到我说了&#xff01;&#xff01;&#xff01;我&#xff01;&#xff01;&#xff01;这一刻&#xff01;&#xff01;&#xff01;我感觉全世界都没有我幸福&#xff01;&#xff01;&#xff01;普通二本学历的我&#xff0c;竟然真的找到了一个16k的工作&#xff01…

面试:正确率能很好的评估分类算法吗

正确率&#xff08;accuracy&#xff09; 正确率是我们最常见的评价指标&#xff0c;accuracy (TPTN)/(PN)&#xff0c;正确率是被分对的样本数在所有样本数中的占比&#xff0c;通常来说&#xff0c;正确率越高&#xff0c;分类器越好。 不同算法有不同特点&#xff0c;在不同…

上传包到npm

切换自己的npm源&#xff08;需要切到npm源 我npm注册的账户&#xff09; 切换为npm源&#xff1a;npm config set registry https://registry.npmjs.org 添加用户 npm adduser 查看是否成功 npm who am i 发包 npm publish 删除指定包版本 npm unpublish 【包名版本号】

springboot745简历系统

springboot745简历系统 获取源码——》公主号&#xff1a;计算机专业毕设大全

绝绝子!你的服务器赞助,我们的开源社区就能飞跃式升级,你绝对不想错过

在当今这个信息爆炸的时代&#xff0c;学习已经成为了我们生活中不可或缺的一部分。然而&#xff0c;随着互联网的高速发展&#xff0c;越来越多的技术知识被涌现出来&#xff0c;让人们在学习的道路上感到困惑和迷茫。为了解决这个问题&#xff0c;我们团队正在开发一个开源项…

尝试以语法对照表格形式学习新语言:c,rust

以语法对照表格形式学习新语言&#xff0c;以rust为例。 关于rust的个人看法&#xff1a; 能否替代c&#xff1f;部分场景可以&#xff0c;长远看并不会。如果c再扩一些关键字&#xff0c;类似cpp的吸星大法式扩充&#xff0c;rust并不具备优势。解决了c的内存管理问题&#x…

【JavaEE】_HTTP响应

目录 1. 首行 2. 报头header 3.空行 4. 正文body 1. 首行 响应首行&#xff1a;版本号状态码状态码描述&#xff1b; HTTP状态码描述了这次响应的结果&#xff08;比如成功、失败&#xff0c;以及失败原因等&#xff09;&#xff1b; 1. HTTP状态码有&#xff1a; &#…

Vue框架与前端部署:构建现代化Web应用的利器

在当今数字化时代&#xff0c;Web应用的开发已成为企业业务发展的关键一环。前端开发作为Web应用的重要组成部分&#xff0c;扮演着至关重要的角色。Vue框架作为一种现代化的前端开发工具&#xff0c;以其简洁易用、高效灵活的特点备受开发者青睐。而前端部署则是将开发的前端代…

Spring中 Bean 的六种作用域官方说明

在 Spring 中有6种 Bean 作用域&#xff0c;分别为&#xff1a; 1、singleton&#xff08;单例作用域&#xff09; 2、prototype&#xff08;原型作用域&#xff09; 3、request&#xff08;请求作用域&#xff09; 4、session&#xff08;会话作用域&#xff09; 5、applicati…

如何在 Linux 系统中查看系统日志

Linux 系统提供了强大的日志功能,可以记录系统和应用程序的各种事件和错误信息。系统日志对于故障排除和性能监控非常重要。 图片 一、使用命令行工具查看系统日志 使用 journalctl 命令查看系统日志:journalctl 命令是 systemd 日志管理器的客户端工具,它可以查看 system…

软件测试知识总结

&#x1f345; 视频学习&#xff1a;文末有免费的配套视频可观看 &#x1f345; 关注公众号&#xff1a;互联网杂货铺&#xff0c;回复1 &#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 1、黑盒测试、白盒测试、灰盒测试 1.1 黑盒测试 黑盒测…

【c++ debug】记一次protobuf结构相关的coredump问题

文章目录 1. 问题现象2. 问题描述3. 问题分析4. 问题根因5. 问题修复6. 补充&#xff1a;类成员变量定义为引用类型 1. 问题现象 其中curr_lanes是一个目标上一帧的当前车道current_lanes_curr_lane是lane_id对应的LaneInfo信息现象&#xff1a;在lane_info->lane().success…

Stackoverflow(1)-根据RequestBody的内容来区分使用哪个资源

如果使用Spring&#xff0c;可以通过RequestBody将请求体的json转换为Java对象&#xff0c;但如果URI相同&#xff0c;而请求体的内容不同&#xff0c;应该怎么办&#xff1f;问题来源(stackoverflow)&#xff1a;Spring RequestBody without using a pojo?稍微研究了一下&…

浅析Linux设备驱动:IO端口和IO内存

文章目录 概述IO端口和IO内存的区别 IO资源管理IO资源类型IO端口资源IO内存资源 IO资源分配 IO端口访问IO端口操作函数 IO内存访问IO内存操作函数 相关参考 概述 在计算机系统中&#xff0c;外部设备通常会提供一组寄存器或内存用于处理器配置和访问设备功能。这些寄存器或内存…

由斐波那契数列探究递推与递归

斐波那契数列定义&#xff1a; 斐波那契数列大家都非常熟悉。它的定义是&#xff1a; 对于给定的整数 x &#xff0c;我们希望求出&#xff1a; f ( 1 ) f ( 2 ) … f ( x ) f(1)f(2)…f(x) f(1)f(2)…f(x) 的值。 有两种方法,分别是递推(迭代)与递归 具体解释如下图 备注…

JDBC核心技术

第1章 JDBC概述 第2章 获取数据库连接 第3章 使用PreparedStatement实现CRUD操作 第4章 操作BLOB类型字段 第5章 批量插入 第6章 数据库事务 第7章 DAO及相关实现类 第8章 数据库连接池 第9章 Apache-DBUtils实现CRUD操作图像 小部件