聊聊Spring事务同步器TransactionSynchronization

在一些业务场景中可能我们需要去对某一个spring事务的生命周期进行监控,比如在这个事务提交,回滚,被挂起的时候,我们想要去执行一些自定义的操作,这怎么去做呢?其实spring作为一个高扩展性的框架,早就提供好了这一个扩展点,这个扩展点就是事务同步器TransactionSynchronization

使用方式 

public interface TransactionSynchronization extends Flushable {int STATUS_COMMITTED = 0;int STATUS_ROLLED_BACK = 1;int STATUS_UNKNOWN = 2;default void suspend() {}default void resume() {}@Overridedefault void flush() {}default void beforeCommit(boolean readOnly) {}default void beforeCompletion() {}default void afterCommit() {}default void afterCompletion(int status) {}}

可以看到,TransactionSynchronization是一个接口,它里面定义了一系列与事务各生命周期阶段相关的方法。比如,我们可以这样使用: 

@Transactional(rollbackFor = Exception.class)
public void saveUser(User user) {TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {@Overridepublic void afterCommit() {System.out.println("saveUser事务已提交...");}});userDao.saveUser(user);
}

在spring事务刚开始的时候,我们通过TransactionSynchronizationManager事务同步管理器去注册一个事务同步器,当spring事务提交完之后,就会去回调当前这个spring事务所注册的所有事务同步器(一个spring事务可以注册多个事务同步器)的afterCommit方法。需要注意的是注册事务同步器必须得在一个spring事务中才能注册,否则会抛出Transaction synchronization is not active这个错误

为何事务同步器必须要在spring事务中才能注册?

 具体原因我们可以通过源码中找到:

public static void registerSynchronization(TransactionSynchronization synchronization)throws IllegalStateException {Assert.notNull(synchronization, "TransactionSynchronization must not be null");Set<TransactionSynchronization> synchs = synchronizations.get();if (synchs == null) {throw new IllegalStateException("Transaction synchronization is not active");}synchs.add(synchronization);
}

 可以看到如果synchronizations.get()返回的是null,那么就会抛出这个错误,而什么时候这里才不会返回null呢?我们可以去看spring事务创建的时候,代码如下:

 

 每一次创建一个新的spring事务的时候都会去调用startTransaction方法,而在startTransaction方法中会调用prepareSynchronization方法,这个方法中做的事情主要就是去把新创建的spring事务的一些信息放到线程上下文中(在这个spring事务执行期间我们都可以通过事务同步管理器去拿到这些信息)。最后最关键的就是调用了initSynchronization方法,在这个方法中我们就看到了此时会初始化一个空集合放到synchronizations中,所以当执行spring事务中的业务代码的时候,此时由于synchronizations已经不为空了,所以我们就可以成功地把事务同步器注册到事务同步器管理器中了

事务同步器在多个事务之间如何切换? 

事务同步器只对注册它的那个spring事务生效,如果这个spring事务中存在嵌套的spring事务,那么事务同步器就不会对嵌套的那个spring事务生效了。这可能有点难理解,我们可以直接上代码去方便理解: 

上面的代码就是saveUser方法会去调用saveUser2方法,其中saveUser2方法的事务传播级别是REQUIRES_NEW,也就是saveUser和saveUser2这两个方法会处于两个不同的事务中,重点是saveUser方法还往事务同步管理器中注册了一个事务同步器去监听事务提交阶段。那么当调用saveUser方法的时候,由于会创建两个事务,此时会不会回调两次事务同步器? 

可以发现事务同步器的afterCommit方法只回调了一次。那么要想回调两次怎么办?答案:在saveUser2方法中也去注册一个事务同步器: 

 执行结果如下:

可以看到只要我们在saveUser2方法中也去注册一个事务同步器,那么当saveUser2的事务提交的时候,就能执行到afterCommit方法了。那么为什么会这样呢?下面我们深入源码去探究:

org.springframework.transaction.support.AbstractPlatformTransactionManager#handleExistingTransaction

private TransactionStatus handleExistingTransaction(TransactionDefinition definition, Object transaction, boolean debugEnabled)
throws TransactionException {// .............// 条件成立:该事务方法所声明的事务传播级别等于PROPAGATION_REQUIRES_NEW,该级别表示需要开启一个新的事务if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW) {if (debugEnabled) {logger.debug("Suspending current transaction, creating new transaction with name [" +definition.getName() + "]");}// 首先需要把原来的事务挂起,这里的挂起其实就是把ThreadLocal中与当前线程上下文绑定的资源对象进行解绑(remove),// 而返回的SuspendedResourcesHolder对象中缓存了原来事务的资源对象SuspendedResourcesHolder suspendedResources = suspend(transaction);try {// 创建一个新的事务return startTransaction(definition, transaction, debugEnabled, suspendedResources);} catch (RuntimeException | Error beginEx) {resumeAfterBeginException(transaction, suspendedResources, beginEx);throw beginEx;}}// ...............
}

当当前线程上下文中存在事务的时候,就会执行handleExistingTransaction方法,在handleExistingTransaction方法中会去判断处理不同的事务传播级别,我们这里以PROPAGATION_REQUIRES_NEW为例子,此时会调用suspend方法,并返回一个挂起的资源对象,我们进去suspend方法看看: 

protected final SuspendedResourcesHolder suspend(@Nullable Object transaction) throws TransactionException {
// 条件成立:说明当前线程上下文中已经存在事务
if (TransactionSynchronizationManager.isSynchronizationActive()) {// 回调线程上下文中所有的事务同步器,并执行其suspend方法,然后再把所有的事务同步器从线程上下文中移除List<TransactionSynchronization> suspendedSynchronizations = doSuspendSynchronization();try {// 被挂起的资源对象Object suspendedResources = null;if (transaction != null) {suspendedResources = doSuspend(transaction);}// 因为需要把当前的事务进行挂起,所以下面要做的就是把线程上下文中的当前事务信息需要被缓存起来,等到事务恢复的时候再从缓存中获取并恢复,并且后面会把新事务的信息放到线程上下文中// 获取被挂起的事务名称String name = TransactionSynchronizationManager.getCurrentTransactionName();// 把线程上下文中的事务名称置为nullTransactionSynchronizationManager.setCurrentTransactionName(null);// 获取被挂起的事务的读写模式boolean readOnly = TransactionSynchronizationManager.isCurrentTransactionReadOnly();// 把线程上下文中的事务读写模式改为非只读TransactionSynchronizationManager.setCurrentTransactionReadOnly(false);// 获取被挂起的事务的事务隔离级别Integer isolationLevel = TransactionSynchronizationManager.getCurrentTransactionIsolationLevel();// 把线程上下文中的事务中的事务隔离级别改为nullTransactionSynchronizationManager.setCurrentTransactionIsolationLevel(null);// 获取被挂起事务的激活状态boolean wasActive = TransactionSynchronizationManager.isActualTransactionActive();// 把线程上下文中的事务激活状态置为falseTransactionSynchronizationManager.setActualTransactionActive(false);// 返回被挂起的资源return new SuspendedResourcesHolder(suspendedResources, suspendedSynchronizations, name, readOnly, isolationLevel, wasActive);} catch (RuntimeException | Error ex) {// doSuspend failed - original transaction is still active...doResumeSynchronization(suspendedSynchronizations);throw ex;}
}
// 条件成立:说明此时线程上下文中还不存在事务
else if (transaction != null) {// 对事务资源进行挂起,具体操作由子类实现,并且返回的是原来事务的连接对象Object suspendedResources = doSuspend(transaction);// 把原来的资源缓存在SuspendedResourcesHolder这个挂起资源对象中return new SuspendedResourcesHolder(suspendedResources);
} else {// Neither transaction nor synchronization active.return null;
}
}
private List<TransactionSynchronization> doSuspendSynchronization() {// 获取当前线程上下文中的所有事务同步器List<TransactionSynchronization> suspendedSynchronizations =TransactionSynchronizationManager.getSynchronizations();// 遍历所有的事务同步器,调用suspend方法for (TransactionSynchronization synchronization : suspendedSynchronizations) {synchronization.suspend();}// 然后把所有的事务同步器都从线程上下文中移除TransactionSynchronizationManager.clearSynchronization();return suspendedSynchronizations;
}

 可以看到此时会把线程上下文中的事务信息以及事务同步器都取出来,然后创建一个SuppendedResourcesHolder对象,把取出来的事务信息以及事务同步器放到这个对象中,最后把这个对象返回出去

/*** 开启事务** @param definition         事务属性定义对象* @param transaction        事务对象* @param debugEnabled       debugEnabled* @param suspendedResources 被挂起的事务的资源对象,如果当前线程中不存在事务,则该参数对象为null*/
private TransactionStatus startTransaction(TransactionDefinition definition, Object transaction,boolean debugEnabled, @Nullable SuspendedResourcesHolder suspendedResources) {// 只要事务同步模式不等于SYNCHRONIZATION_NEVER,那么事务同步在事务开启后都会生效boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);// 创建一个事务状态对象// 这里newTransaction属性很重要,当此时是创建一个新的事务的时候,newTransaction就等于trueDefaultTransactionStatus status = newTransactionStatus(definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);// 具体由子类实现doBegin(transaction, definition);// 事务同步器绑定到当前线程上下文prepareSynchronization(status, definition);return status;
}

然后由于事务传播级别是PROPAGATION_REQUIRES_NEW,所以我们需要新开一个spring事务,也就是调用startTransaction方法创建一个新的TransactionStatus,同样的需要执行prepareSynchronization方法,上面也说过了,在prepareSynchronization方法中会去把新创建的事务的信息放到线程上下文中 

/*** 为给定参数创建TransactionStatus实例*/
protected DefaultTransactionStatus newTransactionStatus(TransactionDefinition definition, @Nullable Object transaction, boolean newTransaction,boolean newSynchronization, boolean debug, @Nullable Object suspendedResources) {boolean actualNewSynchronization = newSynchronization &&!TransactionSynchronizationManager.isSynchronizationActive();return new DefaultTransactionStatus(transaction, newTransaction, actualNewSynchronization,definition.isReadOnly(), debug, suspendedResources);
}

在创建TransactionStatus的构造方法中,此时就会把SuppendedResourcesHolder对象作为参数被存放在DefaultTransactionStatus对象中。这样一来此时就效果就是当前线程上下文中保存的事务就是新创建的事务了,而原来的事务都会缓存到新事务的TransactionStatus对象中了。然后我们关注到事务提交的环节 

private void processCommit(DefaultTransactionStatus status) throws TransactionException {try {// ...........事务提交代码省略} finally {// 这行代码里面处理了恢复被挂起事务的操作,也就是说恢复被挂起的事务是在新事务提交完之后去执行的cleanupAfterCompletion(status);}
}

在提交完事务之后,会执行cleanupAfterCompletion方法 

/*** 当事务完成后会调用,进行一些清理以及恢复操作** @param status 事务状态对象* @see #doCleanupAfterCompletion*/
private void cleanupAfterCompletion(DefaultTransactionStatus status) {// 标记事务状态为已完成status.setCompleted();if (status.isNewSynchronization()) {// 清空当前线程上下文与该事务相关的所有信息TransactionSynchronizationManager.clear();}if (status.isNewTransaction()) {// 钩子方法,子类实现doCleanupAfterCompletion(status.getTransaction());}// 条件成立:说明该事务中有被挂起的事务if (status.getSuspendedResources() != null) {if (status.isDebug()) {logger.debug("Resuming suspended transaction after completion of inner transaction");}Object transaction = (status.hasTransaction() ? status.getTransaction() : null);// 恢复被挂起的事务(把被挂起的事务重新绑定到当前线程上下文中)resume(transaction, (SuspendedResourcesHolder) status.getSuspendedResources());}
}

 此时就会去当前事务的TransactionStatus中查看一下SuspendedResourcesHolder是否存在,如果存在就说明又被挂起的事务,我们需要把这个事务进行恢复(因为事务提交了,之前的事务就需要被恢复)。具体进行事务恢复的逻辑在resume方法中

/*** 恢复给定的事务** @param transaction     当前线程上下文的事务对象* @param resourcesHolder 要恢复的事务* @see #doResume* @see #suspend*/
protected final void resume(@Nullable Object transaction, @Nullable SuspendedResourcesHolder resourcesHolder)throws TransactionException {if (resourcesHolder != null) {// 获取被挂起的资源Object suspendedResources = resourcesHolder.suspendedResources;// 条件成立:被挂起的资源不为nullif (suspendedResources != null) {// 执行具体的事务恢复逻辑,交由子类实现doResume(transaction, suspendedResources);}// 把被挂起的事务的信息放到当前线程上下文中List<TransactionSynchronization> suspendedSynchronizations = resourcesHolder.suspendedSynchronizations;if (suspendedSynchronizations != null) {TransactionSynchronizationManager.setActualTransactionActive(resourcesHolder.wasActive);TransactionSynchronizationManager.setCurrentTransactionIsolationLevel(resourcesHolder.isolationLevel);TransactionSynchronizationManager.setCurrentTransactionReadOnly(resourcesHolder.readOnly);TransactionSynchronizationManager.setCurrentTransactionName(resourcesHolder.name);doResumeSynchronization(suspendedSynchronizations);}}
}

 可以看到就是把之前被挂起的事务的信息以及事务同步器从SuspendedResourcesHolder对象中拿出来,然后重新放到线程上下文中,然后当被挂起的事务提交或回滚的时候,就可以从线程上下文中获取到它的事务同步器然后进行相应的方法回调了

事务同步器的生效条件

事务同步器并不是说总是能生效的,它是基于一个配置属性来判断是否能够生效,这个配置属性在事务管理器中可以进行设置:

private int transactionSynchronization = SYNCHRONIZATION_ALWAYS;
public final void setTransactionSynchronization(int transactionSynchronization) {this.transactionSynchronization = transactionSynchronization;
}

 这个属性有三个值可以进行选择:

/*** 该事务同步模式表示事务同步始终生效** @see org.springframework.transaction.TransactionDefinition#PROPAGATION_SUPPORTS* @see org.springframework.transaction.TransactionDefinition#PROPAGATION_NOT_SUPPORTED* @see org.springframework.transaction.TransactionDefinition#PROPAGATION_NEVER*/
public static final int SYNCHRONIZATION_ALWAYS = 0;/*** 该事务同步模式表示事务同步仅在非“空”事务的时候生效** @see org.springframework.transaction.TransactionDefinition#PROPAGATION_REQUIRED* @see org.springframework.transaction.TransactionDefinition#PROPAGATION_MANDATORY* @see org.springframework.transaction.TransactionDefinition#PROPAGATION_REQUIRES_NEW*/
public static final int SYNCHRONIZATION_ON_ACTUAL_TRANSACTION = 1;/*** 该事务同步模式表示事务同步从不生效*/
public static final int SYNCHRONIZATION_NEVER = 2;

这三个可选值的作用如下:

  • SYNCHRONIZATION_ALWAYS

如果设置了该属性值,则不管什么事务传播级别,事务同步器的方法都能够进行相应的回调,这也是事务管理器的默认值

  • SYNCHRONIZATION_ON_ACTUAL_TRANSACTION

如果设置了该属性值,则只有非“空”事务中的事务同步器才能被回调,什么意思呢?对于比如PROPAGATION_SUPPORTS,PROPAGATION_NOT_SUPPORTED,PROPAGATION_NEVER这三种事务传播级别来说,它们都表示不处于事务中,这样的话就相当于业务代码就不是在事务中执行了,所以也叫做是“空”事务,设置了SYNCHRONIZATION_ON_ACTUAL_TRANSACTION的话,对于“空”事务来说,尽管设置了事务同步器,也不会被回调。因为代码都不在事务中执行了,也就没有所谓的commit和rollback这些事务生命周期阶段了。应用场景可以是如果我们想要只针对监控真实的事务而不是这些“空”事务,那么我们就可以设置该属性值

  • SYNCHRONIZATION_NEVER

如果设置了该属性,不管是真实事务还是“空”事务,都不会回调事务同步器

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

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

相关文章

关系型三大范式与BCNF有什么用呢

学的时候就知道是一堆公式。 实际中在设计表的时候可能会用到。 前提是关系型数据库&#xff0c;比如mysql。 &#xff08;实际中oracle比mysql更好用。但是他收费啊。&#xff09; 第一范式&#xff1a;每个属性都是原子的&#xff08;需要做到每个属性都是不可分割的。&…

02 java ---- Android 基础app开发

目录 相对布局 显示一个美女 显示两个美女 安卓APP启动过程 安卓布局控件 常用布局之相对布局 常用布局之相对布局 padding和margin 按键美化 常用布局之线性布局 安卓按键响应的几种方式 直接设置按键的onClick绑定的函数 自定义类实现按键监听事件的接口 匿名内…

[NLP] LLM---<训练中文LLama2(一)>训练一个中文LLama2的步骤

一 数据集 【Awesome-Chinese-LLM中文数据集】 【awesome-instruction-dataset】【awesome-instruction-datasets】【LLaMA-Efficient-Tuning-数据集】Wiki中文百科&#xff08;25w词条&#xff09;wikipedia-cn-20230720-filteredBaiduBaiKe&#xff08;563w词条&#xff09; …

4.后端·新建子模块与开发(传统模式)

文章目录 学习资料新建子模块与各层查询entity的列表entitymapper层service层controller层 测试 学习资料 https://www.bilibili.com/video/BV13g411Y7GS?p8&spm_id_frompageDriver&vd_sourceed09a620bf87401694f763818a31c91e b站的学习视频 新建子模块与各层 在r…

Linux Ubuntu20.04深度学习环境快速配置命令记录

一、驱动安装 1、更新系统包 sudo apt-get updatesudo apt-get upgrade 2、安装显卡驱动 使用apt方式安装驱动&#xff0c;多数情况不容易成功&#xff0c; 使用一下方法更佳&#xff1a; 1.查看合适显卡的驱动版本 ubuntu-drivers devices NVIDIA GeForce 驱动程序 - …

POJ 3684 Physics Experiment 弹性碰撞

一、题目大意 我们有N个半径为R厘米的球&#xff0c;固定在距离地面高度为H的管道上&#xff0c;刚开始释放第一个&#xff0c;之后每过一秒释放一个&#xff0c;释放下面的球不会影响到上面的球的高度&#xff0c;忽略一切阻力&#xff0c;认为球之间的碰撞为弹性碰撞&#x…

【电子元件】常用电子元器件的识别之电容器

目录 前言1. 电容器的简介2.电容器的识别1. 铝电解电容器2.钽电解电容器3.固态电解电容器4.瓷介电容器5. 贴片陶瓷电容器6. 聚丙烯电容7. 金属化聚丙烯薄膜电容器8. 独石电容器9. 涤纶电容器10. 超小型金属化聚酯薄膜电容器11. 可变电容器11.1 空气可变电容器11.2 薄膜介质可变…

JMeter基础 —— 使用Badboy录制JMeter脚本!

1、使用Badboy录制JMeter脚本 打开Badboy工具开始进行脚本录制&#xff1a; &#xff08;1&#xff09;当我们打开Badboy工具时&#xff0c;默认就进入录制状态。 如下图&#xff1a; 当然我们也可以点击录制按钮进行切换。 &#xff08;2&#xff09;在地址栏中输入被测地…

国庆中秋特辑(一)浪漫祝福方式 用循环神经网络(RNN)或长短时记忆网络(LSTM)生成祝福诗词

目录 一、使用深度学习中的循环神经网络&#xff08;RNN&#xff09;或长短时记忆网络&#xff08;LSTM&#xff09;生成诗词二、优化&#xff1a;使用双向 LSTM 或 GRU 单元来更好地捕捉上下文信息三、优化&#xff1a;使用生成对抗网络&#xff08;GAN&#xff09;或其他技术…

深度学习-卷积神经网络-纹理表示卷积神经网络-卷积神经网络-[北邮鲁鹏]

这里写目录标题 参考文章全连接神经网络全连接神经网络的瓶颈全连接神经网络应用场景 卷积神经网络卷积层(CONV)卷积核卷积操作卷积层设计卷积步长(stride)边界填充特征响应图组尺寸计算 激活层池化层(POOL)池化操作定义池化操作作用池化层超参数常见池化操作 全连接层(FC)样本…

重建与发展:数字资产借贷行业朝着可持续发展迈进!

纵观历史&#xff0c;贷款和货币一样古老&#xff0c;无论哪种形式的货币都需要有其借贷市场。现在&#xff0c;比特币以其分散和透明的性质&#xff0c;在加密领域占据龙头地位。 就像之前的货币一样&#xff0c;比特币要真正蓬勃发展&#xff0c;也需要一个强大的借贷市场。然…

如何在Windows 10/11中重置网络,以及重置后的注意事项有哪些

本文介绍如何在Windows 10和Windows 11中重置网络设置。 如何重置Windows 10网络设置 在Windows10中使用网络重置实用程序相当简单。 一、进入“开始”菜单>“设置”,然后选择“网络和Internet”。 二、在左侧导航窗格中,选择“状态”以确保你正在查看网络状态窗口。然…

嵌入式入门教学——模电基础概念

目录 1、模拟信号和模拟电路 2、研究领域 3、常用术语 3.1、共价键 3.2、电场 3.3、温度的电压当量 3.4、动态信号 3.5、直流电流和交流电流 3.6、内阻 3.7、信号频率 3.8、电容 3.9、电感 3.10、相位 3.11、信号失真 3.12、电导 3.13、跨导 3.14、电位 3.15…

数据结构——图(图的存储及基本操作)

文章目录 前言一、邻接矩阵法&#xff08;顺序存储&#xff09;1.无向图存储邻接矩阵算法2.有向图存储邻接矩阵算法 二、邻接表法(图的链式存储结构)总结 前言 邻接矩阵法(图的顺序存储结构) 1.1 无向图邻接矩阵算法 1.2 有向图邻接矩阵算法邻接表法(图的一种链式存储结构) 一…

Unity WebView 中文输入支持

WebView 中文输入支持 &#x1f96a;效果展示&#x1f371;原理 &#x1f96a;效果展示 &#x1f4a1;使用版本为4.4&#xff1b; &#x1f4a1;测试环境&#xff1a;unity editor 2022.3.15f1c1、Windows&#xff1b; &#x1f371;原理 提取页面激活的输入框&#xff0c;…

github上创建分支并合并到master

github上创建分支并合并到master 目录概述需求&#xff1a; 设计思路实现思路分析1.创建分支2.commit changes3.create pull request按钮4.网页解析器5.数据处理器 参考资料和推荐阅读 Survive by day and develop by night. talk for import biz , show your perfect code,ful…

多线程|多进程|高并发网络编程

一.多进程并发服务器 多进程并发服务器是一种经典的服务器架构&#xff0c;它通过创建多个子进程来处理客户端连接&#xff0c;从而实现并发处理多个客户端请求的能力。 概念&#xff1a; 服务器启动时&#xff0c;创建主进程&#xff0c;并绑定监听端口。当有客户端连接请求…

LLM 04-大模型的数据

LLM 03-大模型的数据 到目前为止&#xff0c;我们已经讨论了大型语言模型的行为&#xff08;能力和损害&#xff09;。现在&#xff0c;我们要剥开洋葱的第一层&#xff0c;开始讨论这些模型是如何构建的。任何机器学习方法的起点都是训练数据&#xff0c;因此这就是我们开始的…

【深度学习】 Python 和 NumPy 系列教程(十三):Matplotlib详解:1、2d绘图(上):折线图、散点图、柱状图、直方图、饼图

目录 一、前言 二、实验环境 三、Matplotlib详解 0、绘图风格 1、2d绘图类型 0. 设置中文字体 1. 折线图&#xff08;Line Plot&#xff09; 2. 散点图&#xff08;Scatter Plot&#xff09; 3. 柱状图&#xff08;Bar Plot&#xff09; 4. 直方图&#xff08;Histogr…

【JavaSE笔记】方法

一、前言 Java中的方法是一种在Java编程中非常常见的概念。 我们可以将方法看作是一种可重复使用的代码块&#xff0c;类似于生活中的工具。就像我们在日常生活中会使用各种各样的工具来完成不同的任务一样&#xff0c;我们在编程中也可以使用方法来完成各种不同的操作。 二…