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

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博客

结尾

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

怎么将经典动漫秒变高清动漫?

你的记忆中是否也有一部经典的动漫、动画片。那是我们童年的美好记忆&#xff0c;但是我们现在如果再去重温时往往会因为太模糊而看不下去&#xff0c;那么我们有什么好的办法可以修复动漫的清晰度呢&#xff1f;一起来看看吧&#xff01; 不管是修复动画片&#xff0c;还是修复…

SelfGNN: Self-Supervised Graph Neural Networks for Sequential Recommendation

SelfGNN: Self-Supervised Graph Neural Networks for Sequential Recommendation&#xff08;Sigir2024&#xff09; 摘要 顺序推荐通过对用户的时间和顺序交互模式进行建模&#xff0c;有效地解决信息过载问题。 为了克服监督信号的局限性&#xff0c;最近的方法在推荐系统中…

Hyper-V如何将文件复制到虚拟机?教您3个简单的方法!

需要将文件复制到虚拟机&#xff01; “大家好&#xff0c;有谁知道Hyper-V怎么将文件复制到虚拟机吗&#xff1f;我有一些文件&#xff0c;想要从主机中复制进虚拟机中&#xff0c;但是我不知道该怎么操作&#xff0c;有谁可以帮帮我吗&#xff1f;谢谢。” Hyper-V虚拟机可…

Vuex遇到浏览器刷新,store里存的数据还在吗?

我们在做Vue前端项目的时候&#xff0c;很可能会使用Vuex来做一些状态或者数据管理&#xff0c;希望在一定程度上&#xff0c;不发送网络请求&#xff0c;不经过密集的组件数据传输&#xff0c;也可以达到数据共享的目的。但如果浏览器页面刷新了&#xff0c;Vuex中store里存的…

​1:25万基础电子地图(江西版)

我们在《50幅1:25万基础电子地图&#xff08;四川版&#xff09;》和《1&#xff1a;25基础电子地图&#xff08;云南版&#xff09;》等文中&#xff0c;为你分享过四川和云南的基础电子地图。 现在我们再为你分享江西的1&#xff1a;25万基础电子地图&#xff0c;你可以在文…

成都某展厅2套2x2透明OLED拼接屏项目

成都某展厅的2套2x2透明OLED拼接屏展示设计具有独特的技术魅力和视觉效果。以下是关于这一展示设计的详细介绍&#xff1a; 1.产品规格 类型&#xff1a;透明OLED拼接屏 尺寸与配置&#xff1a;每套为2x2拼接&#xff0c;即每套由4块屏幕组成。 2.应用场景 成都某展厅&#…

最新下载:Boom 3D软件安装视频教程

简介&#xff1a; Boom 3D是适用于Mac和Windows系统的专业音效增强软件&#xff0c;旨在通过播放器&#xff0c;媒体或流媒体服务等介质&#xff0c;在不同类型的耳机上以3D环绕效果播放媒体内容。您无需使用昂贵的耳机或其他附加环绕音效增强器即可感受3D环绕音乐。 安 装 包…

怎么把网页上的接口信息导入postman

第一步 打开f12&#xff0c;右键选中需要的接口。选择copy-copy as cURL 第二步 打开postman&#xff0c;选择"Raw Text"&#xff0c; 把刚才复制的curl粘贴到空白位置&#xff0c;点击Continue - 最后的效果。导入的接口自带cookie&#xff0c;不用再输入cookie&a…

zotero style最新(可全文翻译)

问题&#xff1a;在下载zotero style的时候&#xff0c;总会出现各种奇奇怪怪的问题&#xff0c;不是期刊没有级别&#xff0c;就是没有IF之类的&#xff1b; 解决&#xff1a;https://github.com/MuiseDestiny/zotero-style/releases 在这里下载最新的版本 若要使用全文翻译…

Matlab进阶绘图第60期—带伪彩图的曲面图

带伪彩图的曲面图是曲面图与伪彩图的组合。 其中&#xff0c;伪彩图与曲面图的颜色用于表示同一个特征。 由于伪彩图无遮挡但不直观&#xff0c;曲面图直观但有遮挡&#xff0c;而将二者组合&#xff0c;可以实现优势互补。 本期就来分享一下带伪彩图的曲面图的绘制方法&…

深入探索Java开发世界:Java基础~类型分析大揭秘

文章目录 一、基本数据类型二、封装类型三、类型转换四、集合类型五、并发类型 Java基础知识&#xff0c;类型知识点梳理~ 一、基本数据类型 Java的基本数据类型是语言的基础&#xff0c;它们直接存储在栈内存中&#xff0c;具有固定的大小和不变的行为。 八种基本数据类型的具…

【WEB前端2024】3D智体编程:乔布斯3D纪念馆-第42课-多人联机-实时互动

【WEB前端2024】3D智体编程&#xff1a;乔布斯3D纪念馆-第42课-多人联机-实时互动 使用dtns.network德塔世界&#xff08;开源的智体世界引擎&#xff09;&#xff0c;策划和设计《乔布斯超大型的开源3D纪念馆》的系列教程。dtns.network是一款主要由JavaScript编写的智体世界…

操作系统入门 -- CPU调度算法

操作系统入门 – CPU调度算法 在了解完进程和线程的概念后&#xff0c;我们就需要了解当一个进程就绪后系统会进行怎样的资源分配并运行进程&#xff0c;因此我们就需要了解CPU的调度算法 1.CPU调度 1.1概念 CPU调度即按照某种算法将CPU资源分配给某个就绪的进程。 1.2调度…

【无标题】Pycharm执行报错

file 读取未指定utf-8编码&#xff0c;加上就好了 疑问&#xff1a;为什么 有的电脑可以直接跑呢&#xff1f;该电脑、Pycharm、工程&#xff0c;已经做了修改设置默认值&#xff0c;但是到新的电脑上&#xff0c;就需要重新设置&#xff0c;所以 file 读、写&#xff0c;最好…

超声波清洗机洗眼镜干净吗?四大珍藏高分超声波清洗机分享!

超声波清洗机就是一种方便快捷且高效的一种智能清洁工具&#xff0c;作为清洁领域的功能集成产品给人们带来了更高质的清洁体验。无论你是注重生活品质还是追求性价比&#xff0c;又或者是探索智能科技产品的爱好者&#xff0c;超声波清洗机肯定不会让你失望的&#xff01;毕竟…

常见的安全测试漏洞

&#x1f345; 视频学习&#xff1a;文末有免费的配套视频可观看 &#x1f345; 点击文末小卡片 &#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 一、SQL注入攻击 SQL 注入攻击主要是由于程序员在开发过程中没有对客户端所传输到服务器端的参…

来自红队大佬的经验之谈---命令执行过滤绕过-Windows篇

感谢来自老流氓大佬的投稿&#xff0c;本次文章介绍的是在windows环境下&#xff0c;过滤的“点”和“空格”等符号&#xff0c;导致在写入webshell时会受限。以下是针对该目标的绕过记录。 首先是命令执行和过滤验证&#xff0c;如下&#xff1a;​ 执行dir命令&#xff0c;…

Windows CSC服务特权提升漏洞(CVE-2024-26229)

文章目录 前言声明一、漏洞描述二、漏洞成因三、影响版本四、漏洞复现五、CVE-2024-26229 BOF六、修复方案 前言 Windows CSC服务特权提升漏洞。 当程序向缓冲区写入的数据超出其处理能力时&#xff0c;就会发生基于堆的缓冲区溢出&#xff0c;从而导致多余的数据溢出到相邻的…

生产 的mybatisplus 日志输入到日志文件

默认是输出到控制台.不输出到日志文件 输出到日志文件.需要修改配置 第一步. logging:config: classpath:logback-wshoto.xml第二步 mybatis-plus:configuration:cache-enabled: truedefault-executor-type: reuselog-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl第三步…

Mathtype与word字号对照+Mathtype与word字号对照

字体大小对照表如下 初号44pt 小初36pt 一号26pt 小一24pt 二号22pt 小二18pt 三号16pt 小三15pt 四号14pt 小四12pt 五号10.5pt 小五9pt 六号7.5pt 小六6.5pt 七号5.5pt 八号5pt 1 保存12pt文件 首选选择第一个公式&#xff0c;将其大小改为12pt 然后依次选择 “预置”—…