Spring事务之AOP导致事务失效问题

news2024/10/7 8:31:15

情况说明

  • 首先开启了AOP,并且同时开启了事务。
  • 下面这个TransactionAspect就是一个简单的AOP切面,有一个Around通知。
@Aspect
@Component
public class TransactionAspect {

	@Pointcut("execution(* com.qhyu.cloud.datasource.service.TransactionService.*(..))") // the pointcut expression
	private void transactionLogInfo() {} // the pointcut signature


	/**
	 * Title:around <br>
	 * Description:这个Around吃掉了异常 <br>
	 * 不太建议吃掉异常,出现这个问题的原因需要排查下为什么?
	 * author:candidate <br>
	 * date:2023/11/10 14:11 <br>
	 * @param
	 * @return
	 */
	@Around("transactionLogInfo()")
	public Object around(ProceedingJoinPoint pjp){
		Object proceed = null;
		System.out.println("TransactionAspect调用目标方法前:@Around");
		try {
			// aop拦截器
			 proceed = pjp.proceed();
		} catch (Throwable e) {
			e.printStackTrace();
		}
		System.out.println("TransactionAspect调用目标方法后:@Around");
		return proceed;
	}
}
  • 创建一个Service和dao,并且需要事务。
public interface TransactionService {

	void doQuery(String id);

	void doUpdate(String id);
}


@Component
public class TransactionServiceImpl implements TransactionService {

	@Autowired
	TransactionDao transactionDao;
  
	@Override
	public void doQuery(String id) {
		System.out.println(transactionDao.UserQuery(id));
	}

	@Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)
	@Override
	public void doUpdate(String id) {
		int i = transactionDao.UserUpdate(id);
		System.out.println("更新用户表信息"+i+"条");
	}
}

@Component
public class TransactionDao {

	@Autowired
	private JdbcTemplate jdbcTemplate;


	// id例子:0008cce0-3c92-45ea-957f-4f6dd568a3e2
	public Object UserQuery(String id){
		return jdbcTemplate.queryForMap("select * from skyworth_user where id = ?",id);
	}


	@SuppressWarnings({"divzero"})
	public int UserUpdate(String id) throws RuntimeException{
		Map<String, Object> resultMap = jdbcTemplate.queryForMap("select * from skyworth_user where id = ?", id);
		int flag = 0;
		if (resultMap.get("is_first_login") == Integer.valueOf("0")){
			flag = 1;
		}
		int update = jdbcTemplate.update("update skyworth_user set is_first_login = ? where id ='0008cce0-3c92-45ea-957f-4f6dd568a3e2' ", flag);
		int i=1/0;
		return update;
	}

}

可以看到TransactionServiceImpl的doUpdate方法是被事务管理的。万事具备,只欠东风。

  • 启动
private static void transactionTest(AnnotationConfigApplicationContext annotationConfigApplicationContext) {
		// 事务回滚了吗,测试事务和aop的时候使用
		TransactionService bean2 = annotationConfigApplicationContext.getBean(TransactionService.class);
		bean2.doQuery("0008cce0-3c92-45ea-957f-4f6dd568a3e2");
		bean2.doUpdate("0008cce0-3c92-45ea-957f-4f6dd568a3e2");
		bean2.doQuery("0008cce0-3c92-45ea-957f-4f6dd568a3e2");
	}

public static void main(String[] args) {
		AnnotationConfigApplicationContext annotationConfigApplicationContext =
				new AnnotationConfigApplicationContext(AopConfig.class);
		transactionTest(annotationConfigApplicationContext);
	}
  • 现象:事务没有回滚,is_first_login标识原来是0,出现异常后数据库显示is_first_login是1,结论就是事务失效了。

问题分析

Spring事物之@EnableTransactionManagemen一章我们说过自动代理创建器是会升级的,所以AnnotationAwareAspectJAutoProxyCreator类就是实现其代理的类。

我这儿TransactionServiceImpl是实现了接口的,并没有强制使用cglib,所以此处用的是JdkDynamicAopProxy生成的代理对象,所以只要我启动项目,就会调用invoke方法。

我需要观察的是Advice的排序问题,因为此处明显是应为Around吃掉了异常才会导致事务失效,但是有时候我们不得不使用Around吃掉异常的时候应该怎么处理就是我们要解决的问题。

所以invoke方法中this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass)就是去获取所有的Advice。

在这里插入图片描述

ExposeInvocationInterceptor的作用就是在方法调用期间将当前代理对象设置到AopContext中。它是整个AOP拦截器链中的第一个拦截器,确保在后续的拦截器或切面中可以通过AopContext获取到当前代理对象。

所以我们需要关注的就是后面两个Interceptors:

  • org.springframework.transaction.interceptor.BeanFactoryTransactionAttributeSourceAdvisor: advice org.springframework.transaction.interceptor.TransactionInterceptor@3918c187
  • InstantiationModelAwarePointcutAdvisor: expression [transactionLogInfo()]; advice method [public java.lang.Object com.qhyu.cloud.aop.aspect.TransactionAspect.around(org.aspectj.lang.ProceedingJoinPoint)]; perClauseKind=SINGLETON

advice调用过程会先调用TransactionInterceptor,然后才会调用ransactionAspect.around。

TransactionInterceptor

首先会调用TransactionInterceptor的invoke方法,代码如下:

  @Override
	@Nullable
	public Object invoke(MethodInvocation invocation) throws Throwable {
		// Work out the target class: may be {@code null}.
		// The TransactionAttributeSource should be passed the target class
		// as well as the method, which may be from an interface.
		Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);

		// Adapt to TransactionAspectSupport's invokeWithinTransaction...
		return invokeWithinTransaction(invocation.getMethod(), targetClass, new CoroutinesInvocationCallback() {
			@Override
			@Nullable
			public Object proceedWithInvocation() throws Throwable {
				// 执行被拦截的方法,也就是加了@transaction注解的方法
				return invocation.proceed();
			}
			@Override
			public Object getTarget() {
				return invocation.getThis();
			}
			@Override
			public Object[] getArguments() {
				return invocation.getArguments();
			}
		});
	}

这个方法非常简单,就是执行并返回方法invokeWithinTransaction的内容。

下面是核心代码,这边进行了精简,为了方便观看。

@Nullable
	protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
			final InvocationCallback invocation) throws Throwable {
	
		// 声明式事务,else逻辑是编程式事务,两种不同的处理方法
		if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {
			// 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 exception
				// 异常回滚事务
				completeTransactionAfterThrowing(txInfo, ex);
				throw ex;
			}
			finally {
				cleanupTransactionInfo(txInfo);
			}

			if (retVal != null && vavrPresent && VavrDelegate.isVavrTry(retVal)) {
				// Set rollback-only in case of Vavr failure matching our rollback rules...
				TransactionStatus status = txInfo.getTransactionStatus();
				if (status != null && txAttr != null) {
					retVal = VavrDelegate.evaluateTryFailure(retVal, txAttr, status);
				}
			}
			// 提交事务
			commitTransactionAfterReturning(txInfo);
			return retVal;
		}

	}

当执行invocation.proceedWithInvocation()的时候将会执行新建的CoroutinesInvocationCallback() 的proceedWithInvocation方法。invocation.proceed();就会开始调用下一个。也就是我们自定义的Around。

其实看到这里就很清楚了,我们应该让Around先执行,或者让Around不吃掉异常才能让事务生效。

TransactionAspect.around

开始执行我们自定义的around方法,方法如下:

@Around("transactionLogInfo()")
	public Object around(ProceedingJoinPoint pjp){
		Object proceed = null;
		System.out.println("TransactionAspect调用目标方法前:@Around");
		try {
			// aop拦截器
			 proceed = pjp.proceed();
		} catch (Throwable e) {
			e.printStackTrace();
		}
		System.out.println("TransactionAspect调用目标方法后:@Around");
		return proceed;
	}

所以会先打印出第一行日志,然后执行pjp.proceed()去调用目的方法doUpdate(),但是目的方法会被吃掉异常,此时执行完成之后再打印Around的第二行日志,最后又回到invokeWithinTransaction,因为异常被吃掉了,所以就直接提交事务了。

排序问题

advice排序这一章我们分析了,order可以改变其排序,具体代码如下:

AbstractAdvisorAutoProxyCreator#findEligibleAdvisors

protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
	
		List<Advisor> candidateAdvisors = findCandidateAdvisors();
		// Around before after afterReturing afterThrowing
		List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
		extendAdvisors(eligibleAdvisors);
		if (!eligibleAdvisors.isEmpty()) {
			// 这里是通过order进行排序的
			eligibleAdvisors = sortAdvisors(eligibleAdvisors);
		}
		return eligibleAdvisors;
	}

查看两个Interceptors的Order,都是2147483647,所以我们可以调整下顺序,让Around在前面执行 ,然后TransactionInterceptor后执行,然后抛出异常后进入到回滚逻辑,最后走Around的后续逻辑。

解决方案

  • 修改我们Advice的Order
@Aspect
@Component
@Order(value = Ordered.HIGHEST_PRECEDENCE+1)
public class TransactionAspect {
  • 修改@EnableTransactionManagement(order=)

  • 调整后结果符合预期

TransactionAspect调用目标方法前:@Around
{id=0008cce0-3c92-45ea-957f-4f6dd568a3e2,  is_first_login=1, lastlanddingtime=null, update_time=2023-10-07 09:24:45}
TransactionAspect调用目标方法后:@Around
java.lang.ArithmeticException: / by zero
	at com.qhyu.cloud.datasource.dao.TransactionDao.UserUpdate(TransactionDao.java:43)
	at com.qhyu.cloud.datasource.service.impl.TransactionServiceImpl.doUpdate(TransactionServiceImpl.java:33)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:344)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:198)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
	at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:128)
	at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:413)
	at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:123)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
	at org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed(MethodInvocationProceedingJoinPoint.java:89)
	at com.qhyu.cloud.aop.aspect.TransactionAspect.around(TransactionAspect.java:48)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:634)
	at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:624)
	at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:72)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
	at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
	at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:224)
	at com.sun.proxy.$Proxy34.doUpdate(Unknown Source)
	at com.qhyu.cloud.QhyuApplication.transactionTest(QhyuApplication.java:88)
	at com.qhyu.cloud.QhyuApplication.main(QhyuApplication.java:22)

TransactionAspect调用目标方法后:@Around

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

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

相关文章

数据结构-链表的简单操作代码实现2【Java版】

目录 写在前&#xff1a; 此篇讲解关于单链表的一些面试题目&#xff0c;续上节。 11.反转一个单链表 12.给定一个带有头结点的head的非空单链表&#xff0c;返回链表的中间结点&#xff0c;如果有两个中间结点&#xff0c;则返回第二个中间结点 13.输入一个链表&#xff0c…

Nginx 使用笔记大全(唯一入口)

Linux服务器因为Nginx日志access.log文件过大项目无法访问 项目处于运行状态下无法访问&#xff0c;第一步查看磁盘状态 1、查看磁盘状态 df -h 2、查找100M以上的文件 find / -size 100M |xargs ls -lh 3、删除文件 rm -rf /usr/local/nginx/logs/access.log 4、配置nginx.…

Linux 性能调优之硬件资源监控

写在前面 考试整理相关笔记博文内容涉及 Linux 硬件资源监控常见的命名介绍&#xff0c;涉及硬件基本信息查看查看硬件错误信息查看虚拟环境和云环境资源理解不足小伙伴帮忙指正 对每个人而言&#xff0c;真正的职责只有一个&#xff1a;找到自我。然后在心中坚守其一生&#x…

centos7下载python3离线安装包

下载离线安装工具 yum install yum-utils -y下载python3离线安装包 yumdownloader --destdir/root/python3 --resolve python3将python3下面的离线安装文件拷贝到其他服务器上就可以安装 离线安装 先进入到离线程序rpm文件存储路径 # 离线安装 rpm -Uvh --force --nodep…

Cnyunwei

运维管理系统&#xff1a;监控系统 Cnyunwei Centos 6 封装 Cacti、Nagios、Centreon&#xff08;中英文自己切换&#xff09;、Check_MK、Nconf英文版本全部采用与国外官方同步的最新版本&#xff0c;会发布32位和64位两个版本。 安装很简单&#xff0c;直接回车即可全自动安…

AttributeError: module ‘matplotlib‘ has no attribute ‘get_data_path‘

【报错】使用 AutoDL 下 Notebook 调用 matplotlib 时遇到 AttributeError: module matplotlib has no attribute get_data_path 报错&#xff1a; --------------------------------------------------------------------------- AttributeError …

《深入浅出进阶篇》——空间换时间优化——P2671 求和

链接&#xff1a;https://www.luogu.com.cn/problem/P2671 上题干&#xff1a; 题目描述 一条狭长的纸带被均匀划分出了n个格子&#xff0c;格子编号从11到n。每个格子上都染了一种颜色colori​用[1,m]当中的一个整数表示&#xff09;&#xff0c;并且写了一个数字numberi​。…

asp.net core mvc之 布局

一、布局是什么&#xff1f; 布局是把每个页面的公共部分&#xff0c;提取成一个布局页面&#xff08;头、导航、页脚&#xff09;。 二、默认布局 _Layout.cshtml 默认的布局是在 /Views/Shared 目录的 _Layout.cshtml文件。通常Shared目录中的视图都是公共视图。该目录下的…

部分背包问题【贪心算法】

部分背包问题是一种经典的贪心问题&#xff0c;物品可以取一部分&#xff0c;也就是可以随意拆分的物品。 算法思路&#xff1a; 用列表保存每个物品的价值及总重量、平均价值&#xff08;性价比&#xff09;。输入数据同时计算每种物品的平均价值。使用自定义的compare函数以…

PADS快速调整器件的位号

选择元器件&#xff0c; ctrlA 全选器件&#xff0c;右击菜单选择特性 如下三个信息&#xff0c;确认 配置标签信息&#xff0c;如图界面信息&#xff0c;点击应用&#xff0c;器件全部归位

Slax Linux 强化了会话管理和引导参数选项

导读Slax Linux 的创始人和维护者Tomas Matejicek 宣布 了他的微型和便携 GNU/Linux 发行版的新版本&#xff0c;带来了各种增强和错误修复。 新的 Slax Linux 版本&#xff08;基于 Debian 的 12.1 版本和基于 Slackware 的 15.0.2 版本&#xff09;引入了在可写设备上运行发…

vue3 ref 与shallowRef reactive与shallowReactive

ref 给数据添加响应式&#xff0c;基本类型采用object.defineProperty进行数据劫持&#xff0c;对象类型是借助reactive 实现响应式&#xff0c;采用proxy 实现数据劫持&#xff0c;利用reflect进行源数据的操作 let country ref({count:20,names:[河南,山东,陕西],objs:{key…

AI大模型时代,开发工程师与项目管理者面对的机遇和挑战,文末送书3本

&#x1f3c6;作者简介&#xff0c;黑夜开发者&#xff0c;CSDN领军人物&#xff0c;全栈领域优质创作者✌&#xff0c;CSDN博客专家&#xff0c;阿里云社区专家博主&#xff0c;2023年6月CSDN上海赛道top4。 &#x1f3c6;数年电商行业从业经验&#xff0c;历任核心研发工程师…

centos7 在线安装python3

在线安装命令 yum install -y python3 输入命令之后等待安装完成 查看版本 查看版本3 输入命令 python3 看到版本号为3.6.8 查看版本2 输入命令 python2 看到版本号为2.7.5

网络运维Day13

文章目录 部署web服务器部署虚拟机web1安装依赖包解压NGINX压缩包初始化编译编译安装查看验证配置动静分离 部署虚拟机web2安装依赖包解压NGINX压缩包初始化编译编译安装查看验证配置动静分离 配置NGINX七层代理测试健康检查功能 配置NGINX四层代理部署代理服务器 总结 部署web…

vue中cli组件如何自定义定义

目录 创建自定义组件 注册并使用自定义组件 注册组件&#xff1a; 在需要使用该组件的页面或父组件中注册并引入自定义组件。 使用 Props 传递数据 总结步骤&#xff1a; 前言 在Vue CLI中使用自定义组件是构建交互式和模块化Web应用的重要一环。Vue CLI为开发者提供了使用…

RocketMQ底层通信机制

分布式系统各个角色间的通信效率很关键&#xff0c;通信效率的高低直接影响系统性能&#xff0c;基于Socket实现一个高效的TCP通信协议是很有挑战的&#xff0c;本节介绍RocketMQ是如何解决这个问题的。 1.Remoting模块 RocketMQ的通信相关代码在Remoting模块里&#xff0c;先…

Linkage Mapper 报错

1 . 错误提示&#xff1a;“No module named lm_config” 错误原因&#xff1a;**** 2.错误提示&#xff1a;“Cannot find an installation of Circuitscape in your Program Files directory.” 错误原因&#xff1a;***** 3. 错误提示&#xff1a;UnicodeEncodeError: ‘asc…

Windows 微PE WePE_64_V2.3 PE模式下启用账号和修改密码

启动PE后&#xff0c;进入桌面打开运行dism程序 选择带有系统的盘符&#xff08;默认选的是PE盘&#xff09;&#xff0c;然后打开会话 选择左侧工具箱&#xff0c;然后右侧找到账户管理 然后就可以对已有账号进行管理了 结束。

Java SE 封装、包、static关键字和代码块

1.封装 1.1封装的概念 面向对象程序三大特性&#xff1a;封装、继承、多态。而类和对象阶段&#xff0c;主要研究的就是封装特性。何为封装呢&#xff1f;简单来说 就是套壳屏蔽细节。 封装&#xff1a;将数据和操作数据的方法进行有机结合&#xff0c;隐藏对象的属性和实现细…