一、场景分析

最近做项目遇到了一个很奇怪的问题,大致的业务场景是这样的:我们首先设定两个事务,事务parent和事务child,在Controller里边同时调用这两个方法,示例代码如下:

1、场景A:

这里其实是分别执行了两个事物,执行的结果是两个方法都可以插入数据!如下:

2、场景B:

修改上述代码如下:

Propagation.REQUIRES_NEW的含义表示:如果当前存在事务,则挂起当前事务并且开启一个新事物继续执行,新事物执行完毕之后,然后在缓刑之前挂起的事务,如果当前不存在事务的话,则开启一个新事物。
执行的结果是两个方法都可以插入数据!执行结果如下:

场景A和场景B都是正常的执行,期间没有发生任何的回滚,假如child()方法中出现了异常!

3、场景C

修改child()的代码如下所示,其他代码和场景B一样:

执行结果如下,会出现异常,并且数据都没有插入进去:

疑问1:场景C中child()抛出了异常,但是parent()没有抛出异常,按道理是不是应该parent()提交成功而child()回滚?

可能有的小伙伴要说了,child()抛出了异常在parent()没有进行捕获,造成了parent()也是抛出了异常了的!所以他们两个都会回滚!

4、场景D

按照上述小伙伴的疑问这个时候,如果对parent()方法修改,捕获child()中抛出的异常,其他代码和场景C一样:

然后再次执行,结果是两个都插入了数据库:

看到这里很多小伙伴都可能会问,按照我们的逻辑来想的话child()中抛出了异常,parent()没有抛出并且捕获了child()抛出了异常!执行的结果应该是child()回滚,parent()提交成功的啊!

疑问2:场景D为什么不是child()回滚和parent()提交成功哪?

上述的场景C和场景D似乎融为了一题,要么都成功要么都失败!和我们预期的效果一点都不一样!看到这里这就是我们今天要探讨的主题《JDK动态代理给Spring事务埋下的坑!》接下来我们就分析一下Spring事物在该特定场景下不能回滚的深层次原因!

二、问题本质所在
我们知道Spring事务管理是通过JDK动态代理的方式进行实现的(另一种是使用CGLib动态代理实现的),也正是因为动态代理的特性造成了上述parent()方法调用child()方法的时候造成了child()方法中的事务失效!简单的来说,在场景D中parent()方法调用child()方法的时候,child()方法的事务是不起作用的,此时的child()方法像一个没有加事务的普通方法,其本质上就相当于下边的代码:

场景C本质:

场景D本质:

正如上述的代码,我们可以很轻松的解释疑问1和疑问2,因为动态代理的特性造成了场景C和场景D的本质如上述代码。在场景C中,child()抛出异常没有捕获,相当于parent事务中抛出了异常,造成parent()一起回滚,因为他们本质是同一个方法;在场景D中,child()抛出异常并进行了捕获,parent事务中没有抛出异常,parent()和child()同时在一个事务里边,所以他们都成功了;

看到这里,那么动态代理的这个特性到底是什么才会造成Spring事务失效那?

三、动态代理的这个特性到底是什么?
首先我们看一下一个简单的动态代理实现方式:

此时我们执行以下测试方法,注意了此时是同时调用了test1()和test2()的,执行结果如下:

可以看出,在OrderServiceImpl 类中由于test1()没有调用test2(),他们方法的执行都是使用了代理的,也就是说test1和test2都是通过代理对象调用的invoke()方法,这和我们场景A和B类似。

加入我们模拟一下场景C和场景D在test1()中调用test2(),那么代码修改为如下:

执行结果如下:

这里可以很清楚的看出来test1()走的是代理,而test2()走的是普通的方法,没有经过代理!看到这里你是否已经恍然大明白了呢?

这个应该可以很好的理解为什么是这样子!这是因为在Java中test1()中调用test2()中的方法,本质上就相当于把test2()的方法体放入到test1()中,也就是内部方法,同样的不管你嵌套了多少层,只有代理对象proxy 直接调用的那一个方法才是真正的走代理的,如下:

测试方法和上边的测试方法一样,执行结果如下:

记住:只有代理对象proxy直接调用的那个方法才是真正的走代理的!

四、如何解决这个坑?
上文的分析中我们已经了解了为什么在该特定场景下使用Spring事务的时候造成事务无法回滚的问题,下边我们谈一下几种解决的方法:

1、我们可以选择逃避这个问题!我们可以不使用以上这种事务嵌套的方式来解决问题,最简单的方法就是把问题提到Service或者是更靠前的逻辑中去解决,使用service.xxxtransaction是不会出现这种问题的。

2、通过AopProxy上下文获取代理对象:

(1)SpringBoot配置方式:注解开启 exposeProxy = true,暴露代理对象 (否则AopContext.currentProxy()) 会抛出异常。

添加依赖:

添加注解:

修改原有代码的执行方式为:

此时的执行结果为:

可见,child方法由于异常已经回滚了,而parent可以正确的提交,这才是我们想要的结果!注意的是在parent调用child的时候是通过try/catch捕获了异常的!

(2)传统Spring XML配置文件只需要添加依赖个设置如下配置即可,使用方式一样:

<aop:aspectj-autoproxy expose-proxy="true"/>

3、通过ApplicationContext上下文进行解决:

执行结果符合我们的预期:

五、总结
到此为止,我们简单的介绍了一下Spring事务管理中如果业务中有像场景C或者场景D的情况时,如果不清楚JDK动态代理造成Spring事务无法回滚的问题的话就可能是一个开发事故了,说不定是要扣工资的!
---------------------

原文地址:https://blog.csdn.net/bntX2jSQfEHy7/article/details/79040349

JDK动态代理给Spring事务埋下的坑!的更多相关文章

  1. (转)面试必备技能:JDK动态代理给Spring事务埋下的坑!

    一.场景分析 最近做项目遇到了一个很奇怪的问题,大致的业务场景是这样的:我们首先设定两个事务,事务parent和事务child,在Controller里边同时调用这两个方法,示例代码如下: 1.场景A ...

  2. 通过JDK动态代理实现 Spring AOP

    1.新建一个目标类 接口:public interface IUserService //切面编程 public void addUser(); public void updateUser( ); ...

  3. 有点深度的聊聊JDK动态代理

    在接触SpringAOP的时候,大家一定会被这神奇的功能所折服,想知道其中的奥秘,底层到底是如何实现的.于是,大家会通过搜索引擎,知道了一个陌生的名词:动态代理,慢慢的又知道了动态代理有多种实现方式, ...

  4. AOP jdk动态代理

    一: jdk动态代理是Spring AOP默认的代理方法.要求 被代理类要实现接口,只有接口里的方法才能被代理,主要步骤是先创建接口,接口里创建要被代理的方法,然后定义一个实现类实现该接口,接着将被代 ...

  5. Spring声明式事务的实现方式选择(JDK动态代理与cglib)

    1.简介 Spring声明式事务的具体实现方式是动态决定的,与具体配置.以及事务代理对象是否实现接口等有关. 2.使用JDK动态代理的情况 在满足下面两个条件时,Spring会选择JDK动态代理作为声 ...

  6. Spring AOP详解 、 JDK动态代理、CGLib动态代理

    AOP是Aspect Oriented Programing的简称,面向切面编程.AOP适合于那些具有横切逻辑的应用:如性能监测,访问控制,事务管理以及日志记录.AOP将这些分散在各个业务逻辑中的代码 ...

  7. jdk动态代理与cglib代理、spring aop代理实现原理

    原创声明:本博客来源与本人另一博客[http://blog.csdn.net/liaohaojian/article/details/63683317]原创作品,绝非他处摘取 代理(proxy)的定义 ...

  8. jdk动态代理与cglib代理、spring aop代理实现原理解析

    原创声明:本博客来源为本人原创作品,绝非他处摘取,转摘请联系博主 代理(proxy)的定义:为某对象提供代理服务,拥有操作代理对象的功能,在某些情况下,当客户不想或者不能直接引用另一个对象,而代理对象 ...

  9. 何为代理?jdk动态代理与cglib代理、spring Aop代理原理浅析

    原创声明:本博客来源为本人原创作品,绝非他处摘取,转摘请联系博主 代理(proxy)的定义:为某对象提供代理服务,拥有操作代理对象的功能,在某些情况下,当客户不想或者不能直接引用另一个对象,而代理对象 ...

随机推荐

  1. 【Java每日一题】20170324

    20170323问题解析请点击今日问题下方的“[Java每日一题]20170324”查看(问题解析在公众号首发,公众号ID:weknow619) package Mar2017; public cla ...

  2. 自己用HashMap来模拟一个Session缓存(简易版)

    本文记录:Hibernate中一级缓存的特点. 一级缓存的细节什么操作会向一 1.级缓存放入数据 save,update,saveOrUpdate,load,get,list,iterate,lock ...

  3. c3p0链接池配置使用

    c3p0链接池初步使用:直接上代码 c3p0是开源面粉的连接池,目前使用它的开源项目主要有:Spring,Hibernate等,使用时需要导入相关jar包及配置文件c3p0-config.xml文件 ...

  4. es6 语法 (字符串扩展)

    { console.log('a',`\u0061`); //a,a console.log('s',`\u20BB7`); //s ₻7 console.log('s',`\u{20BB7}`) / ...

  5. Spider-two

    一.网络数据加密:1. md5 / sha1 不可逆加密算法: 结果是十六进制数, 结果不可逆, 多用于文件验证 import hashlib md5_obj = hashlib.md5() sha1 ...

  6. @RequestBody Spring MVC 示例

    1.前端的访问请求 <script type="text/javascript"> $(document).ready(function(){ var saveData ...

  7. Django之初识Ajax

    1.简介 AJAX(Asynchronous Javascript And XML)翻译成中文就是“异步的Javascript和XML”.即使用Javascript语言与服务器进行异步交互,传输的数据 ...

  8. C#:如何使方法过时,如何否决方法

    在使用.Net Frameworkd进行开发时,经常可以在方法的智能提示ToolTip上面看到一个方法是[否决的],如图: 或者在警告里面: 甚至使用[否决的]方法的时候还会造成编译错误: 上面的这些 ...

  9. JS 调试中常见的报错的解决办法

    报错:Uncaught SyntaxError: Unexpected token o in JSON at position 1 at JSON.parse (<anonymous>) ...

  10. 腾讯云Centos安装gitlab

    参考了网上很多人写的安装教程,结果并不好,最后阅读了官方的英文api,才安装成功,这里记录下来,方便以后使用.我的安装环境为腾讯云主机Centos7.3 64bit gitlab官方api地址点我试试 ...