【源码】Spring事务之事务失效及原理

news2024/12/23 14:47:59

Spring事务

1、【源码】SpringBoot事务注册原理

2、【源码】Spring Data JPA原理解析之事务注册原理

3、【源码】Spring Data JPA原理解析之事务执行原理

4、【源码】SpringBoot编程式事务使用及执行原理

5、【源码】Spring事务之传播特性的详解

6、【源码】Spring事务之事务失效及原理

前言

在前面的Spring事务序列博文中分享了Spring事务注册、事务执行原理、编程式事务使用及原理。然后,如果使用不当,依然会导致事务失效。本篇分享一下常见的Spring事务失效的场景,并分析失败的原因。

访问权限问题

Java的访问权限主要有四种:private、default(包级访问权限)、protected、public。它们用于控制类、方法和变量的访问级别,限定了对应成员的可见性。

如果在非public的方法中添加@Transactional注解事务,则事务会失效。

2.1 示例

如以下代码:

@Service
public class MemberStatisticsService {
 
    @Resource
    private MemberStatisticsRepository memberStatisticsRepository;
 
    @Transactional
    private int addStatistics(MemberStatisticsEntity entity) {
        // 省略其他
        int rs = memberStatisticsRepository.save(entity);
        return rs ? 1 : 0;
    }
 
}

在addStatistics()方法被调用的时候,会执行TransactionAspectSupport的invokeWithinTransaction(),在该方法中,会调用TransactionAttributeSource的getTransactionAttribute()方法,获取TransactionAttribute对象。该方法会调用AbstractFallbackTransactionAttributeSource的computeTransactionAttribute()方法,从而调用具体的事务解析器,获得TransactionAttribute对象。

2.2 原理

AbstractFallbackTransactionAttributeSource的computeTransactionAttribute()方法的源码如下:

public abstract class AbstractFallbackTransactionAttributeSource
		implements TransactionAttributeSource, EmbeddedValueResolverAware {

	/**
	 * 从方法的目标方法、目标类或原方法、原方法的类中查找Transaction的属性信息,没有找到返回null
	 */
	@Nullable
	protected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class<?> targetClass) {
		// 如果只允许公共方法才能拥有事务,则进行public判断
		if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
			return null;
		}

		// 找到具体的方法。如果当前方法是一个接口方法,需要找到目标类中的实现。如果targetClass为null,那么该方法不会改变
		Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
		// 查找方法的Transaction属性,抽象方法,由子类实现
		TransactionAttribute txAttr = findTransactionAttribute(specificMethod);
		if (txAttr != null) {
			return txAttr;
		}

		// Transaction属性可能配置在目标类中
		txAttr = findTransactionAttribute(specificMethod.getDeclaringClass());
		if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {
			return txAttr;
		}

		// 如果找到的目标方法和当前方法不同,即当前方法为接口方法或被重写的方法
		if (specificMethod != method) {
			// 再次从原方法中查找Transaction属性信息
			txAttr = findTransactionAttribute(method);
			if (txAttr != null) {
				return txAttr;
			}
			// 如果还没有找到,从原方法的定义类中查找Transaction属性信息
			txAttr = findTransactionAttribute(method.getDeclaringClass());
			if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {
				return txAttr;
			}
		}

		return null;
	}

}

在computeTransactionAttribute()方法中,会先判断对应的方法是否为public,如果不是,直接返回null,在TransactionAspectSupport.createTransactionIfNecessary()方法中就不会开启事务。

源码如下:

public abstract class TransactionAspectSupport implements BeanFactoryAware, InitializingBean {

	/**
	 * 如有必要,根据给定的TransactionAttribute创建事务
	 */
	@SuppressWarnings("serial")
	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) {
				@Override
				public 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");
				}
			}
		}
		// 准备TransactionInfo对象,并返回
		return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
	}
}

详细见:

【源码】Spring Data JPA原理解析之事务执行原理-CSDN博客

所以,如果要是@Transactional事务生效,方法必须定义为public访问权限。

无效异常

在项目中,为了规范编程,使用了自定义异常,如果使用不当,也容易导致事务失效。

1)首先自定义的异常不能直接继承Exception,因为事务默认只处理RuntineException或Error异常;

2)通过@Transactional的rollbackFor参数设置回滚异常时,指定了自定义的异常;

3.1 示例

@Slf4j
@Service
public class GoodsService {

    @Resource
    private GoodsRepository goodsRepository;

    @Resource
    private GoodsDetailRepository goodsDetailRepository;

    @Transactional(rollbackFor = BusinessException.class)
    public GoodsEntity save(GoodsEntity entity, GoodsDetailEntity detail) {
        entity.setCreateTime(new Date());
        try {
            entity = goodsRepository.save(entity);
            detail.setId(entity.getId());
            detail = goodsDetailRepository.save(detail);
            entity.setDetail(detail);
        } catch (Exception e) {
            throw new BusinessException(e.getMessage());
        }
        // 省略其他
        return entity;
    }
}

以上代码,如果在try-catch以外报了别的异常,那么会导致事务失效。

3.2 原理

3.2.1 TransactionAspectSupport异常回滚处理

TransactionAspectSupport的相关源码如下:

public abstract class TransactionAspectSupport implements BeanFactoryAware, InitializingBean {
    /**
	 * 执行回滚
	 */
	protected void completeTransactionAfterThrowing(@Nullable TransactionInfo txInfo, Throwable ex) {
		if (txInfo != null && txInfo.getTransactionStatus() != null) {
			if (logger.isTraceEnabled()) {
				logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() +
						"] after exception: " + ex);
			}
			// 如果满足回滚规则
			if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) {
				try {
					// 进行事务回滚
					txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
				}
				catch (TransactionSystemException ex2) {
					logger.error("Application exception overridden by rollback exception", ex);
					ex2.initApplicationException(ex);
					throw ex2;
				}
				catch (RuntimeException | Error ex2) {
					logger.error("Application exception overridden by rollback exception", ex);
					throw ex2;
				}
			}
			else {
				// We don't roll back on this exception.
				// Will still roll back if TransactionStatus.isRollbackOnly() is true.
				try {
					// 如果TransactionStatus.isRollbackOnly()为true,则仍将回滚
					txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
				}
				catch (TransactionSystemException ex2) {
					logger.error("Application exception overridden by commit exception", ex);
					ex2.initApplicationException(ex);
					throw ex2;
				}
				catch (RuntimeException | Error ex2) {
					logger.error("Application exception overridden by commit exception", ex);
					throw ex2;
				}
			}
		}
	}
}

当异常导致事务回滚时,要先通过TransactionAttribute.rollbackOn()判断对应异常是否满足回滚规则,如果不满足,依然会提交事务。rollbackOn()是接口方法,实现如下:

对于@Transactional注解,使用的是RuleBasedTransactionAttribute,该类继承DefaultTransactionAttribute。

3.2.2 RuleBasedTransactionAttribute.rollbackOn()

对应源码如下:

 public class RuleBasedTransactionAttribute extends DefaultTransactionAttribute implements Serializable {   
    /**
	 * 重写父类的方法,重写定义了是否需要回滚
	 * @param ex
	 * @return
	 */
	@Override
	public boolean rollbackOn(Throwable ex) {
		if (logger.isTraceEnabled()) {
			logger.trace("Applying rules to determine whether transaction should rollback on " + ex);
		}

		RollbackRuleAttribute winner = null;
		// deepest为最大值,相当于不管异常有多深,只要规则中有此异常都应该回滚
		int deepest = Integer.MAX_VALUE;

		// 如果有回滚规则,从列表中查找是否有能够匹配该异常的规则
		if (this.rollbackRules != null) {
			for (RollbackRuleAttribute rule : this.rollbackRules) {
				int depth = rule.getDepth(ex);
				if (depth >= 0 && depth < deepest) {
					deepest = depth;
					winner = rule;
				}
			}
		}

		if (logger.isTraceEnabled()) {
			logger.trace("Winning rollback rule is: " + winner);
		}

		// User superclass behavior (rollback on unchecked) if no rule matches.
		// 如果没有找到匹配的回滚规则,则返回父类的执行结果,即只回滚RuntimeException 或者 Error(比如OOM这种)
		if (winner == null) {
			logger.trace("No relevant rollback rule found: applying default rules");
			return super.rollbackOn(ex);
		}
		// 如果有匹配的回滚规则,则属于不需要回滚的,则返回false,即不回滚
		return !(winner instanceof NoRollbackRuleAttribute);
	}
}

DefaultTransactionAttribute.rollbackOn()源码如下:

public class DefaultTransactionAttribute extends DefaultTransactionDefinition implements TransactionAttribute {
    /**
	 * 是否回滚。只回滚RuntimeException 或者 Error(比如OOM这种)
	 */
	@Override
	public boolean rollbackOn(Throwable ex) {
		return (ex instanceof RuntimeException || ex instanceof Error);
	}
}

在RuleBasedTransactionAttribute.rollbackOn()方法中,会先判断异常信息是否在@Transactional中指定,如果没有,调用父类的rollbackOn()进行判断,父类DefaultTransactionAttribute的rollbackOn()方法,判断异常是否属于RuntimeException或Error,如果是,才会回滚事务。

业务异常捕获

不管是通过@Transactional注解实现事务还是编程式事务,都是在业务逻辑出现异常时,事务处理会捕获异常,并判断是否要进行事务回滚,然后抛出对应异常。如果业务异常自己捕获了,没有往外抛,也会导致事务失效。

4.1 事务失效示例

@Slf4j
@Service
public class GoodsService {

    @Resource
    private GoodsRepository goodsRepository;

    @Resource
    private GoodsDetailRepository goodsDetailRepository;

    @Transactional
    public GoodsEntity save(GoodsEntity entity, GoodsDetailEntity detail) {
        entity.setCreateTime(new Date());
        try {
            entity = goodsRepository.save(entity);
            detail.setId(entity.getId());
            detail = goodsDetailRepository.save(detail);
            entity.setDetail(detail);
        } catch (Exception e) {
            log.error(e.getMessage());
            return null;
        }
        return entity;
    }
}

4.2 事务失效修改示例

@Slf4j
@Service
public class GoodsService {

    @Resource
    private GoodsRepository goodsRepository;

    @Resource
    private GoodsDetailRepository goodsDetailRepository;

    @Transactional
    public GoodsEntity save(GoodsEntity entity, GoodsDetailEntity detail) {
        entity.setCreateTime(new Date());
        try {
            entity = goodsRepository.save(entity);
            detail.setId(entity.getId());
            detail = goodsDetailRepository.save(detail);
            entity.setDetail(detail);
        } catch (Exception e) {
            log.error(e.getMessage());
            // 设置事务回滚
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
            return null;
        }
        return entity;
    }
}

只需要在异常捕获的地方加上如下代码:

TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();

4.3 原理

事务在提交之前,会再做一次是否回滚判断,源码如下:

package org.springframework.transaction.support;

@SuppressWarnings("serial")
public abstract class AbstractPlatformTransactionManager implements PlatformTransactionManager, Serializable {

	@Override
	public final void commit(TransactionStatus status) throws TransactionException {
		if (status.isCompleted()) {
			throw new IllegalTransactionStateException(
					"Transaction is already completed - do not call commit or rollback more than once per transaction");
		}

		DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;
		// 如果本地代码设置了回滚
		if (defStatus.isLocalRollbackOnly()) {
			if (defStatus.isDebug()) {
				logger.debug("Transactional code has requested rollback");
			}
			processRollback(defStatus, false);
			return;
		}

		// 全局事务被标记为仅回滚,但事务代码请求提交
		if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) {
			if (defStatus.isDebug()) {
				logger.debug("Global transaction is marked as rollback-only but transactional code requested commit");
			}
			processRollback(defStatus, true);
			return;
		}
		// 提交处理
		processCommit(defStatus);
	}

}

在commit()方法中,在真正提交处理前,会先进行两个判断:

1)defStatus.isLocalRollbackOnly()如果返回true,会执行回滚;

2)判断!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly(),如果为true,也会执行回滚;

内部方法调用

5.1 示例

@Service
public class MemberStatisticsService {
 
    @Resource
    private TransactionTemplate transactionTemplate;
 
    @Resource
    private MemberStatisticsRepository memberStatisticsRepository;
 
    public int addStatistics(MemberStatisticsEntity entity) {
        boolean rs = addStatisticsBs(entity);
        // 省略其他
        return rs ? 1 : 0;
    }
 
	@Transactional
    public int addStatisticsBs(MemberStatisticsEntity entity) {
		// 省略其他
		memberStatisticsRepository.save(entity);
        return true;
	}
 
}

如果是通过addStatistics()方法,方法没有添加@Transactional注解,然后调用带@Transactional注解的addStatisticsBs()方法时,当addStatisticsBs()出现异常时,事务不会回滚。

5.2 原理

【源码】SpringBoot事务注册原理-CSDN博客

在上面的博客中介绍了方法中添加@Transactional注解时,该类会生成代理类,代理类中添加了TransactionInterceptor拦截器,从而实现了事务管理。

当addStatistics()方法执行时,会先执行ReflectiveMethodInvocation.proceed()方法,循环遍历所有的拦截器。执行完所有拦截器之后,再执行动态代理对象的target类的对应方法,即原方法。详见:

【源码】Spring Data JPA原理解析之Repository执行过程及SimpleJpaRepository源码-CSDN博客

博客中的动态代理方法拦截部分。

因为addStatistics()没有添加@Transactional注解,所以执行target的addStatistics()方法,所以在addStatistics()方法内部的this对象是target,而不是代理对象。所以在addStatistics()内部调用addStatisticsBs()方法时,是执行target的addStatisticsBs()方法,所以不再先执行ReflectiveMethodInvocation.proceed(),也就不会执行TransactionInterceptor拦截器,所以没有开启事务管理。

传播特性使用不当

事务传播特性的详细说明见

【源码】Spring事务之传播特性的详解-CSDN博客

结尾

限于篇幅,本篇先分享到这里。

关于本篇内容你有什么自己的想法或独到见解,欢迎在评论区一起交流探讨下吧。

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

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

相关文章

语音识别相关文章整理目录

一、语音大模型架设与功能实现 使用sherpa-ncnn进行中文语音识别&#xff08;ubuntu22&#xff09;-CSDN博客文章浏览阅读953次&#xff0c;点赞30次&#xff0c;收藏26次。请注意&#xff0c;需要首先安装安装了所有必要的依赖项&#xff0c;包括 CMake、Git 和一个合适的 C/…

风控中的文本相似方法之余弦定理

一、余弦相似 一、 余弦相似概述 余弦相似性通过测量两个向量的夹角的余弦值来度量它们之间的相似性。0度角的余弦值是1&#xff0c;而其他任何角度的余弦值都不大于1&#xff1b;并且其最小值是-1。 从而两个向量之间的角度的余弦值确定两个向量是否大致指向相同的方向。结…

vite|webpack环境变量-多模式配置

vite官方文档&#xff1a;环境变量和模式 | Vite (vitejs.net) &#xff08;https://www.vitejs.net/guide/env-and-mode.html&#xff09; 一、工程根目录创建env文件如下&#xff1a; 文件中参数书写格式&#xff1a; VITE_XXXXXX XXXXXX。必须使用等号。必须以VITE_开头…

《地下城与勇士》新手攻略,开荒必备!云手机多开教程!

《地下城与勇士》&#xff08;DNF&#xff09;是一款广受欢迎的多人在线动作角色扮演游戏。玩家将在游戏中扮演不同职业的角色&#xff0c;通过打怪、做任务、PK等方式不断提升自己&#xff0c;探索广阔的阿拉德大陆。游戏中设有丰富的副本、装备、技能系统&#xff0c;玩家可以…

程序员画图工具?那必然是你了!!【送源码】

作为一个程序员&#xff0c;画图是必不可少的技巧。当然此画图不是搞艺术&#xff0c;而是画各种架构图、流程图、泳道图以及各种示意图。 平时我不论是记笔记、写技术文章&#xff0c;还是工作中写文档&#xff0c;都需要配上各种各样的示意图。不管是帮助自己更好的掌握知识…

「6.18福利」精选大厂真题|笔试刷题陪伴|明天正式开屋啦 - 打卡赢价值288元丰厚奖励

&#x1f370;关于清隆学长 大家好&#xff0c;我是清隆&#xff0c;拥有ACM区域赛 银牌&#x1f948;&#xff0c;CCCC天梯赛 国一&#xff0c;PTA甲级 98 分。 致力于算法竞赛和算法教育已有 3 年&#xff0c;曾多次 AK 互联网大厂笔试&#xff0c;大厂实习经验丰富。 打卡…

示例:WPF中使用DecodePixelHeight和DecodePixelWidth优化Image性能

一、目的&#xff1a;在使用Image控件时&#xff0c;如果图片太大或者图片数量过多时加载出来的程序内存会非常的大&#xff0c;但一般图片多时我们只要预览缩略图就可以&#xff0c;查看时再显示原图&#xff0c;这个时候需要通过通过设置BitmapImage的DecodePixelHeight和Dec…

Postgresql配置SSL连接

1、系统需要有openssl、openssl-devel包 yum -y install openssl openssl-devel 2、查看当前数据库是否使用openssl编译 pg_config|grep CONFIGURE 如果没有重新编译 make clean make && make install 3、服务器端证书配置 服务器端需生成三个文件: root.crt(根证…

浏览器调试小技巧

一. 使用XSwitch工具代理本地服务地址 1. 谷歌提供了一个扩展程序: XSwitch 工具描述: 一个重定向URL并允许CORS使本地开发体验轻松愉快的工具。 ps: 这个工具只有谷歌有, 只能翻墙后下载 安装成功后 长这样: 2. 全局安装http-server , 用于在本地启动一个服务 npm i http-…

白帽子最喜欢用什么渗透测试工具?看看哪些是你用过的

一、白帽子最喜欢用什么安全工具? 2020 年的 HackerOne 黑客报告中,统计过白帽子们最喜欢用的软硬件工具。 从图中可以看到,89% 的白帽子都会使用 Burp Suite 这个 Web 应用安全测试工具,有 39% 会尝试自己写工具,第三名的 Fuzzers 是模糊测试工具。再后面主要是一些代理…

Dart 弱引用进阶

前言 村里的老人说&#xff1a;“真正的强者&#xff0c;都是扮猪吃老虎。” 日常开发中经常需要用到弱引用&#xff0c;Dart 语言里也有提供弱引用的接口 WeakReference&#xff0c;我们可以基于它开发更强大的复杂结构。 在前面的文章中&#xff0c;我们用到了一个以弱引用…

现代易货:创新交易模式引领物品交换新潮流

在繁华的现代经济浪潮中&#xff0c;物品交换的文化逐渐崭露头角&#xff0c;引领了一种新颖的交易潮流——现代易货交易模式。这种模式不仅是对古老“以物易物”交易的现代化诠释&#xff0c;更是对物品价值多元化和交换方式创新的深入探索。那么&#xff0c;现代易货交易究竟…

螺丝工厂vtk ThreadFactory(1)

螺丝工厂vtkThreadFactory (1) 缘起 几年前的探索在Python里应用Openscad实现3D建模之3D螺纹建模初探3 新的参考: generating nice threads in openscadvtkRotationalExtrusionFilter 辅助AI: coze 笔记&#x1f4d2;: openscad 代码分析 // 半径缩放函数&#xff0c;用…

国货骄傲精亿内存条颠覆游戏战场,推出超强DDR5 7200玄武系列电竞内存

随着科技的迅猛发展,对高性能电脑的需求不断增长,特别是在电竞领域。认识到这一点,国货知名品牌精亿(JINGYI)推出了其全新一代DDR5 7200 RGB电竞内存条,并命名系列为象征中国上古四大神兽的玄武-系列。这款产品凭借其卓越性能和令人印象深刻的海力士A-DIE颗粒配置,正在迅速成为…

环信beta版鸿蒙IM SDK发布!深度适配HarmonyOS NEXT系统

环信beta版鸿蒙IM SDK已正式发布&#xff01;欢迎有需求开发者体验集成&#xff01; 版本亮点 提供原生鸿蒙 SDK&#xff0c;支持原生 ArkTS 语言&#xff0c;全面拥抱鸿蒙生态提供鸿蒙系统上单聊、群聊、会话等能力和服务覆盖消息管理、用户属性、群租管理、离线推送.多设备…

【C++】认识STL

【C】认识STL STL的概念STL的版本STL的六大组件STL的三个境界STL的缺陷 STL的概念 SLT(standard template libaray-标准模板库)&#xff1a;是C标准库的重要组成部分&#xff0c;不仅是一个可复用的组件库&#xff0c;而且是一个保罗数据结构与算法的软件框架。 STL的版本 原…

深入学习html的步骤

推荐的学习步骤&#xff1a; 1. 深入了解HTML基础标签 列表 HTML提供有序列表(<ol>)和无序列表(<ul>)。 <h2>无序列表</h2> <ul><li>项目一</li><li>项目二</li><li>项目三</li> </ul><h2>…

【2024最新华为OD-C/D卷试题汇总】[支持在线评测] 火星字符串(100分) - 三语言AC题解(Python/Java/Cpp)

&#x1f36d; 大家好这里是清隆学长 &#xff0c;一枚热爱算法的程序员 ✨ 本系列打算持续跟新华为OD-C/D卷的三语言AC题解 &#x1f4bb; ACM银牌&#x1f948;| 多次AK大厂笔试 &#xff5c; 编程一对一辅导 &#x1f44f; 感谢大家的订阅➕ 和 喜欢&#x1f497; &#x1f…

函数的一点点习题

1、利用递归计算0-n的和 #include <stdio.h> #include <string.h> #include <stdlib.h> int rec(int n) {if(n0)return 0;elsereturn nrec(n-1); } int main(int argc, const char *argv[]) {int n0;printf("please enter n:");scanf("%d&quo…

Ollama:本地部署大模型 + LobeChat:聊天界面 = 自己的ChatGPT

本地部署大模型 在本地部署大模型有多种方式&#xff0c;其中Ollama方式是最简单的&#xff0c;但是其也有一定的局限性&#xff0c;比如大模型没有其支持的GGUF二进制格式&#xff0c;就无法使用Ollama方式部署。 GGUF旨在实现快速加载和保存大语言模型&#xff0c;并易于阅读…