消失的事务

端午节前,组内在讨论一个问题:

一个没有加@Transactional注解的方法,去调用一个加了@Transactional的方法,会不会产生事务?

文字苍白,还是用代码说话。

先写一个@Transactional的方法(本文的所有代码,可到Github上下载):

@Transactional
public void deleteAllAndAddOneTransactional(Customer customer) {
customerRepository.deleteAll();
if ("Yang".equals(customer.getFirstName())) {
throw new RuntimeException();
}
customerRepository.save(customer);
}

方法内先去执行deleteAll(),删除表中全部数据;然后执行save()保存数据。

这两个方法中间,会判断传进来的firstName是不是等于“Yang”,是,则抛异常,用于模拟两个数据库操作之间可能发生的异常场景。

如果没有加@Transactional注解,那么这两个操作就不在一个事务里面,不具有原子性。如果deleteAll之后抛异常,那么就会导致只删除不新增。

而加了@Transactional之后,这两个动作在一个事务里头,具有原子性,要么全部成功,要么全部失败。如果deleteAll之后抛异常,则事务回滚,恢复原先被删除的数据。

测试一下,启动Spring Boot工程,首先调用findAll接口,看看数据库中都有哪些数据:

接着调用deleteAndSave接口,故意传入firstName=”Yang”,果然返回失败:

然后,在回过头去调用findAll接口,看看数据是不是还在:

数据都在,说明产生事务了。

上面都没啥,都跟符合我们的直觉。

问题来了,如果我的接口是去调用一个没有加@Transactional的方法,然后这个方法再去调用加了@Transactional的方法呢?

public void deleteAllAndAddOne(Customer customer) {
System.out.println("go into deleteAllAndAddOne");
deleteAllAndAddOneTransactional(customer);
} @Transactional
public void deleteAllAndAddOneTransactional(Customer customer) {
customerRepository.deleteAll();
if ("Yang".equals(customer.getFirstName())) {
throw new RuntimeException();
}
customerRepository.save(customer);
}

直觉告诉我,会的。

重新编译,启动,调用新的接口,继续故意让它抛异常:

然后再去findAll,看看数据还在不在:

WTF! 空空如也!数据都没了!

看来我又一次被直觉欺骗了。

还是得老老实实看代码,弄懂原理。

看了一晚上代码,恍然大悟。咱们先画个图解释一下,再来看看代码。

图解@Transactional

首先,我们得先弄懂@Transactional的原理。

为什么第一种情况,也就是直接调用@Transactional方法,会产生事务?

其实Spring的@Transactional,跟Spring AOP一样,都是利用了动态代理。

我们写了一个类,里面写了一个加了@Transactional注解的方法,这原本平淡无奇,什么用也没有,就像这样

关键在于,Spring在检查到@Transactional注解之后,给这个对象生成了一个代理对象proxy:

代理对象的methodB,会先开启事务(beginTransaction),然后再去执行原先对象target的methodB,如果抛异常,则回滚(rollBack),如果一切顺利,则提交(commit)。

而最后注入Spring容器的,也正是这个带有事务逻辑的代理对象。所以我们调用methodB时会产生事务。

现在,我们写了一个新方法,methodA,里头去调用methodB:

从上面的分析,可以知道,我们最终拿到的,是代理对象。

那么代理对象的methodA是长什么样子的呢?长这样:

由于methodA没有加@Transactional注解,所以代理对象里面,直接就是target.methodA(),直接调用了原来对象的methodA。

这下就很清晰了,代理对象的methodA,去调用原来对象的methodA,原来对象的methodA,再去调用原来对象的methodB,而原来对象的methodB,是不具有事务的。事务只存在于代理对象的methodB. 所以整个方法也就没有事务了。

看看代码

最后再来看看代码。

只需要在deleteAllAndAddOneTransactional方法内,打一个断点,一切了然。

分别调用两个接口,比较调用堆栈:

明显可以看出,直接调用@Transactional方法,堆栈更长,而且会经过一个叫TransactionInterceptor的拦截器。

跟着堆栈往上走,会发现关键就在于这个if-else的逻辑,CglibAopProxy:

CglibAopProxy会去检查要调用的方法,有没有AOP调用链:

  • 没有,则走if里面的逻辑,直接调用target对象的方法,也就是上面间接调用@Transactional方法的情形;
  • 有,则走else逻辑,也就是直接调用@Transactional方法的情形。

当然,如果deleteAllAndAddOne方法被别的切面拦截,那么调用链chain也不会为空,也会走if逻辑,这时候是否会有事务呢?思考题。

上面贴的代码是Cglib代理的情形,JDK Proxy的,大家自行欣赏。

总结

这篇文章主要讲了一个有点违反直觉的现象。

通过这样一个例子,希望能够加深大家对Spring动态代理的理解。

现在想想,面试时,为什么那么喜欢问源码?

其实道理很简单,你用了这项技术、这个框架,却不知道它是怎么实现的,那就有可能造成错误的使用。

玩转Spring--消失的事务@Transactional的更多相关文章

  1. Spring 之注解事务 @Transactional

    众所周知的ACID属性:  原子性(atomicity).一致性(consistency).隔离性(isolation)以及持久性(durability).我们无法控制一致性.原子性以及持久性,但可以 ...

  2. 【@Transactional】Spring 之注解事务 @Transactional

    spring 事务注解 默认遇到throw new RuntimeException("...");会回滚 需要捕获的throw new Exception("...&q ...

  3. Spring 之注解事务 @Transactional(转载)

    Spring在TransactionDefinition接口中规定了7种类型的事务传播行为, 它们规定了事务方法和事务方法发生嵌套调用时事务如何进行传播: 事务传播行为类型 事务传播行为类型 说明 P ...

  4. Spring @Cacheable注解 && 事务@Transactional 在同一个类中的方法调用不生效

    @Cacheable 注解在对象内部调用不会生效 代码示例:ProductServiceImpl.java public List<ProductInfoVO> getProductLis ...

  5. Spring声明式事务@Transactional 详解,事务隔离级别和传播行为

    @Transactional注解支持9个属性的设置,这里只讲解其中使用较多的三个属性:readOnly.propagation.isolation.其中propagation属性用来枚举事务的传播行为 ...

  6. 玩转spring boot——结合JPA事务

    接着上篇 一.准备工作 修改pom.xml文件 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi=&q ...

  7. Spring声明式事务管理基于@Transactional注解

    概述:我们已知道Spring声明式事务管理有两种常用的方式,一种是基于tx/aop命名空间的xml配置文件,另一种则是基于@Transactional 注解.         第一种方式我已在上文为大 ...

  8. 对于spring中事务@Transactional注解的理解

    现在spring的配置都喜欢用注解,这边就说下@Transactional 一.如何开启@Transactional支持 要使用@Transactional,spring的配置文件applicatio ...

  9. 【Spring】16、注解事务 @Transactional

    概述 事务管理对于企业应用来说是至关重要的,即使出现异常情况,它也可以保证数据的一致性.Spring Framework对事务管理提供了一致的抽象,其特点如下: 为不同的事务API提供一致的编程模型, ...

随机推荐

  1. JMeter工具学习(二)——获取登录 token

    备注: JMeter版本4.0 JDK版本1.8 1,新增线程组 2,添加http请求(如何添加Http请求查看详情) 3,添加正则表达式提取器(regular expression extracto ...

  2. Mysql 数据库 表中列的操作

    [1]Mysql数据库中表的列操作 Mysql中关于表中列的操作集语句: -- [1]增加一列 ) DEFAULT NULL COMMENT '目的码区号'; -- [2]增加一列,在dnis_are ...

  3. 洛谷P3957:跳房子——题解

    https://www.luogu.org/problem/P3957 沉迷普及组题无法自拔. 显然二分答案,然后里面套个dp,$f[i]$表示跳到第$i$个格子的最大得分,复杂度$O(n^2logn ...

  4. Java | Spring Boot Swagger2 集成REST ful API 生成接口文档

      Spring Boot Swagger2 集成REST ful API 生成接口文档 原文 简介 由于Spring Boot 的特性,用来开发 REST ful 变得非常容易,并且结合 Swagg ...

  5. 关于mysql的null相关查询的一些坑

    我们先看一下效果,然后在解释,示例如下: mysql> create table test5 (a int not null,b int,c varchar(10)); Query OK, 0 ...

  6. FusionInsight大数据开发---Streaming应用开发

    Streaming应用开发 掌握Streaming基本业务开发流 熟悉Streaming常用API接口使用 掌握Streaming业务设计基本原则 了解Streaming应用开发环境 了解CQL开发流 ...

  7. JAVAWEB之增删改查

    青年志愿者服务网(20分)   1.项目需求: 为了适应社会主义市场经济发展的需要,推动青年志愿服务体系和多层次社会保障体系的建立和完善,促进青年健康成长,石家庄铁道大学急需建设青年志愿者服务网,推进 ...

  8. IEDA 启动main报 stock

    1 . 启动资源管理器关闭java进程,重新启动还是没有解决问题 2.看网上说jdk版本1.8换成1.7 启动成功,已解决,不知道为为什么.

  9. Net操作RabbitMQ

    原文:Net操作RabbitMQ 文章目录 1 安装 2 管理界面 3 RabbitMQ的基本概念 4 RabbitMQ的六种工作模式 4.1 简单模式 4.2 工作模式 4.3 发布/订阅模式 4. ...

  10. C#完成 使用异步线程定时更新窗体标签内容,并对标签内容进行求和显示

    这是我在面试过程中遇到的一个小测试,很可惜,当天未能圆满完成,虽然第二天经过实际测试已经OK 了,但学无止境,继续努力吧,特将此代码贴在这里,供以后学习使用: namespace mytest { p ...