spring-transaction源码分析(3)Transactional事务失效原因

37 篇文章 1 订阅
29 篇文章 1 订阅
当在@Transactional方法中使用this调用另一个@Transactional方法时,由于SpringAOP的代理模式,拦截器无法捕获内部调用,可能导致事务失效。解决方法是切换到AdviceMode.ASPECTJ模式,以实现更高级的拦截。文章通过代码示例解释了问题的原因并提出了解决方案。
摘要由CSDN通过智能技术生成

问题概述

在Transactional方法中使用this方式调用另一个Transactional方法时,拦截器无法拦截到被调用方法,严重时会使事务失效。

类似以下代码:

@Transactional
public void insertBlogList(List<Blog> blogList) {
  for (Blog blog : blogList) {
    this.blogMapper.insertBlog(blog);
  }
  try {
    TimeUnit.SECONDS.sleep(15);
  } catch (InterruptedException e) {
    e.printStackTrace();
  }
}

@Transactional
public void deleteBlogByCondition(BlogSearchParameter parameter) {
  List<Blog> blogs = this.blogMapper.selectBlogByParameter(parameter);
  for (Blog blog : blogs) {
    this.blogMapper.deleteBlog(blog.getId());
  }
  // 抛出一个RuntimeException
  throw new RuntimeException("deleteBlogByCondition抛出一个异常");
}

@Transactional
public void insertAndDeleteBlogList2(List<Blog> blogList, BlogSearchParameter parameter) {

  // 插入数据
  this.insertBlogList(blogList);

  // 删除数据
  try {
    this.deleteBlogByCondition(parameter);
  } catch (Exception e) {
    System.err.printf("Err:%s%n", e.getMessage());
  }

  System.out.println("继续插入数据");

  // 继续插入数据
  this.insertBlogList(blogList);
}

正常情况下,执行到"继续插入数据"时会抛出一个"rollback only"的异常,然后事务回滚。

而现在的现象是:

  • 三个操作都不会开启事务,出现异常也不会回滚
  • "删除数据"操作会把符合条件的数据都删除掉
  • "继续插入数据"操作会再插入数据

原因分析

在EnableTransactionManagement注解mode属性的文档中:

The default is AdviceMode.PROXY. Please note that proxy mode allows for interception of calls through the proxy only. Local calls within the same class cannot get intercepted that way; an Transactional annotation on such a method within a local call will be ignored since Spring's interceptor does not even kick in for such a runtime scenario. For a more advanced mode of interception, consider switching this to AdviceMode.ASPECTJ.

大概意思是:mode属性的默认值是AdviceMode.PROXY,这种方式仅允许通过代理对来调用事务方法,同一个类的本地调用无法被事务切面拦截。如果要解决这个问题,可以使用AdviceMode.ASPECTJ模式。

其实这个问题的根本原因与spring-tx无关,而是spring-aop的实现方式造成的。

从spring-aop拦截器分析问题原因

在DynamicAdvisedInterceptor和JdkDynamicAopProxy中有一段类似的代码:

在这里插入图片描述

在这里插入图片描述

其中target就是原始的业务层Bean对象。

在后续创建ReflectiveMethodInvocation/CglibMethodInvocation时又将此target传递了进去:

// JDK
MethodInvocation invocation =
		new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
retVal = invocation.proceed();

// Cglib
retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();

proceed方法中在拦截器链最后会调用目标方法:

public Object proceed() throws Throwable {
	// We start with an index of -1 and increment early.
	if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
		return invokeJoinpoint();
	}

	// 略
}

protected Object invokeJoinpoint() throws Throwable {
	// 反射调用目标方法
	// 这个target就是原始Bean对象
	return AopUtils.invokeJoinpointUsingReflection(this.target, this.method, this.arguments);
}

所以如果在目标方法中使用this方法调用另一个需要被拦截的方法,将不会执行拦截逻辑。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值