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

打印 上一主题 下一主题

主题 939|帖子 939|积分 2817

问题概述

在Transactional方法中使用this方式调用另一个Transactional方法时,拦截器无法拦截到被调用方法,严重时会使事务失效。
类似以下代码:
  1. @Transactional
  2. public void insertBlogList(List<Blog> blogList) {
  3.   for (Blog blog : blogList) {
  4.     this.blogMapper.insertBlog(blog);
  5.   }
  6.   try {
  7.     TimeUnit.SECONDS.sleep(15);
  8.   } catch (InterruptedException e) {
  9.     e.printStackTrace();
  10.   }
  11. }
  12. @Transactional
  13. public void deleteBlogByCondition(BlogSearchParameter parameter) {
  14.   List<Blog> blogs = this.blogMapper.selectBlogByParameter(parameter);
  15.   for (Blog blog : blogs) {
  16.     this.blogMapper.deleteBlog(blog.getId());
  17.   }
  18.   // 抛出一个RuntimeException
  19.   throw new RuntimeException("deleteBlogByCondition抛出一个异常");
  20. }
  21. @Transactional
  22. public void insertAndDeleteBlogList2(List<Blog> blogList, BlogSearchParameter parameter) {
  23.   // 插入数据
  24.   this.insertBlogList(blogList);
  25.   // 删除数据
  26.   try {
  27.     this.deleteBlogByCondition(parameter);
  28.   } catch (Exception e) {
  29.     System.err.printf("Err:%s%n", e.getMessage());
  30.   }
  31.   System.out.println("继续插入数据");
  32.   // 继续插入数据
  33.   this.insertBlogList(blogList);
  34. }
复制代码
正常情况下,执行到"继续插入数据"时会抛出一个"rollback only"的异常,然后事务回滚。
而现在的现象是:

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

在EnableTransactionManagement注解mode属性的文档中:
  1. The default is AdviceMode.PROXY. Please note that proxy mode allows for interception of calls through the proxy only.
  2. Local calls within the same class cannot get intercepted that way; an Transactional annotation on such a method within
  3. a local call will be ignored since Spring's interceptor does not even kick in for such a runtime scenario.
  4. 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传递了进去:
  1. // JDK
  2. MethodInvocation invocation =
  3.                 new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
  4. retVal = invocation.proceed();
  5. // Cglib
  6. retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
复制代码
proceed方法中在拦截器链最后会调用目标方法:
  1. public Object proceed() throws Throwable {
  2.         // We start with an index of -1 and increment early.
  3.         if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
  4.                 return invokeJoinpoint();
  5.         }
  6.         // 略
  7. }
  8. protected Object invokeJoinpoint() throws Throwable {
  9.         // 反射调用目标方法
  10.         // 这个target就是原始Bean对象
  11.         return AopUtils.invokeJoinpointUsingReflection(this.target, this.method, this.arguments);
  12. }
复制代码
所以如果在目标方法中使用this方法调用另一个需要被拦截的方法,将不会执行拦截逻辑。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

商道如狼道

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表