前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >被标记为事务的方法互相调用的坑(上)

被标记为事务的方法互相调用的坑(上)

作者头像
java架构师
发布2019-03-06 17:31:31
6630
发布2019-03-06 17:31:31
举报
文章被收录于专栏:Java架构师进阶Java架构师进阶

相信大家一定用过Spring中的注解型事务,配合上Spring Boot,只需要在方法上打一个@Transactional 就可以完成,真香。

但是如果大家对其中的机制一知半解的话,可能一不小心就会掉进坑,然后久久无法爬出来。

下面我就分享下 被标记为事务的方法互相调用的坑

如果想学习Java工程化、高性能及分布式、深入浅出。微服务、Spring,MyBatis,Netty源码分析的朋友可以加我的Java高级交流:854630135,群里有阿里大牛直播讲解技术,以及Java大型互联网技术的视频免费分享给大家。

首先我写两个事务方法:

@AutowiredAccountMapper mapper;@Transactional@OverridepublicvoidinsertCodeBear(){ Account account =newAccount(); account.setAccount("CodeBear"); account.setPassword("CodeBear"); mapper.insert(account); }@Transactional@OverridepublicvoidinsertCodeMonkey(){ Account account =newAccount(); account.setAccount("CodeMonkey"); account.setPassword("CodeMonkey"); mapper.insert(account); }

现在我想在insertCodeBear方法里面调用insertCodeMonkey方法,但是insertCodeMonkey不是很重要,就算失败,也不能影响到insertCodeBear方法的执行,但是insertCodeMonkey该回滚的还是要回滚,我们很容易写出如下代码:

@AutowiredAccountMapper mapper;@Transactional@OverridepublicvoidinsertCodeBear(){try{ insertCodeMonkey(); }catch(Exception ex) { } Account account =newAccount(); account.setAccount("CodeBear"); account.setPassword("CodeBear"); mapper.insert(account); }@Transactional(propagation = Propagation.REQUIRES_NEW)@OverridepublicvoidinsertCodeMonkey(){ Account account =newAccount(); account.setAccount("CodeMonkey"); account.setPassword("CodeMonkey"); mapper.insert(account);inta =1/0;//自杀代码,便于测试}

在第二个方法中,用了自杀代码,便于测试。

看上去一点问题都没有:第一个方法会成功,第二个方法会失败并且回滚。但是仅仅是看上去,当我们运行一下,会发现奇怪的事情发生了:

两个方法竟然都成功了!!Why?

如果想学习Java工程化、高性能及分布式、深入浅出。微服务、Spring,MyBatis,Netty源码分析的朋友可以加我的Java高级交流:854630135,群里有阿里大牛直播讲解技术,以及Java大型互联网技术的视频免费分享给大家。

为了排查问题,需要开启一下 有关事务 的日志,在 配置文件 中加上下面的配置:

logging.level.org.springframework.jdbc.datasource.DataSourceTransactionManager=debug

然后运行,看下控制台打印的内容:

图片可能有点模糊,大家可以在新标签页中打开这图片,可以看到这里分明只开了一个事务,而且事务的传播行为是PROPAGATION_REQUIRED,这是事务的默认传播行为,也就是这里只开启了insertCodeBear方法的事务,并没有开启insertCodeMonkey的事务。

这是什么原因?为了更好的说明问题产生的原因,我需要手写一个AOP。

在此之前大家要达成一个共识,@Transactional 其实也是通过AOP去实现的。

AOP有几种实现方式,我这里采用JDK动态代理的方式:

代码入口:

publicclassMain{public static void main(String[] args) {BookServiceImplimpl =newBookServiceImpl();InvocationHandlermyInvocationHandler=newMyInvocationHandler(impl);Objecto=Proxy.newProxyInstance(myInvocationHandler.getClass().getClassLoader(),impl.getClass().getInterfaces(),myInvocationHandler); ((IBookService)o).add(); }}

接口:

public interface IBookService { voidadd(); void delete();}

实现类:

publicclassBookServiceImplimplementsIBookService{publicvoidadd() {delete(); System.out.println("add"); }publicvoiddelete() { System.out.println("delete"); }}

切面定义:

publicclass MyInvocationHandler implements InvocationHandler {privateObjectobj;publicMyInvocationHandler(Objectobj) {this.obj = obj; }publicObjectinvoke(Objectproxy, Method method,Object[] args)throwsThrowable { System.out.println("开始啦,小伙子"); method.invoke(obj, args); System.out.println("结束啦,小伙子");returnnull; }}

在Main入口里面调用了实现类的代理对象,调用了add方法,add方法里面又调用了delete的方法。很简单吧。按照我们的想法,应该是打印出两次 切面中定义的话,但是事实是 只打印了一次:

让我们在切面方法中加上这行代码:

System.out.println("方法是" +method.getName());

看看是哪个方法进入到了这里。

运行:

add方法进入到了这里,但是delete方法却没有进来。

让我们再回到第一个例子,为了让大家看的清楚一点,我再贴上insertCodeBear被调用的代码:

@RestController@RequestMapping("/CodeBear")public class HelloWorldController {@AutowiredAccountService service;@GetMapping("/insert") public void insert() {service.insertCodeBear(); }}

AccountService 是一个接口,里面定义了insertCodeBear和insertCodeMonkey虚方法。

我们打一个断点在

service.insertCodeBear();

这里,然后调试看下service是一个什么东西:

你会发现,service已经不是简单的AccountService 的实现类了,而是实现类的代理对象,从这里也可以看出,其实@Transactional也是通过AOP去实现的。

通过两个例子,可以得到一个结论:只有调用代理对象的方法才能被拦截,所以 在方法A中直接调用方法B,方法B是不会被拦截的

这也就是为什么insertCodeMonkey的事务没有被开启的原因了,因为insertCodeMonkey方法是insertCodeBear直接调用的。

那么,这个问题该如何解决呢?在下一篇博客,我会采用几种方式来解决这个问题(这篇博客已经比较长了,因为加上了很多看上去没什么用的“废话”,因为可以直接写出结论,然后再写解决方案就是了。但是我还是很详细的,把“废话”都写出来了,就是因为分析问题的思路才是最重要的 )。

如果想学习Java工程化、高性能及分布式、深入浅出。微服务、Spring,MyBatis,Netty源码分析的朋友可以加我的Java高级交流:854630135,群里有阿里大牛直播讲解技术,以及Java大型互联网技术的视频免费分享给大家。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019.02.21 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
云直播
云直播(Cloud Streaming Services,CSS)为您提供极速、稳定、专业的云端直播处理服务,根据业务的不同直播场景需求,云直播提供了标准直播、快直播、云导播台三种服务,分别针对大规模实时观看、超低延时直播、便捷云端导播的场景,配合腾讯云视立方·直播 SDK,为您提供一站式的音视频直播解决方案。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档