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

问题概述

在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中有一段类似的代码:

spring-transaction源码分析(3)Transactional事务失效原因_第1张图片

spring-transaction源码分析(3)Transactional事务失效原因_第2张图片

其中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方法调用另一个需要被拦截的方法,将不会执行拦截逻辑。

你可能感兴趣的:(Spring,Spring,Boot,java技术,spring,java,spring,boot)