消失的事务

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

一个没有加@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. leetcode 111. 二叉树的最小深度

    题目描述: 给定一个二叉树,找出其最小深度. 最小深度是从根节点到最近叶子节点的最短路径上的节点数量. 说明: 叶子节点是指没有子节点的节点. 示例: 给定二叉树 [3,9,20,null,null, ...

  2. git cherry-pick 命令,解决冲突后没有 commit 变更,再次pull 时出错

    Git : You have not concluded your cherry-pick (CHERRY_PICK_HEAD exists). 解决方法:(1)使用git status 命令查看当前 ...

  3. [转帖]kubernetes 常见问题整理

    kubernetes 常见问题整理 https://www.cnblogs.com/qingfeng2010/p/10642408.html 使用kubectl 命令报错 报错: [root@k8s- ...

  4. Linux 常用文件描述

    Linux 常用文件描述 /etc/issue 本地登陆显示的信息,本地登录前 /etc/issue.net 网络登陆显示的信息,登录后显示,需要由sshd配置 /etc/motd 常用于通告信息,如 ...

  5. Spring AOP动态代理实现,解决Spring Boot中无法正常启用JDK动态代理的问题

    Spring AOP底层的动态代理实现有两种方式:一种是JDK动态代理,另一种是CGLib动态代理. JDK动态代理 JDK 1.3版本以后提供了动态代理,允许开发者在运行期创建接口的代理实例,而且只 ...

  6. drools规则语法(一)

    1.基本的匹配规则 1.1变量 drools使用匹配的方式对Fact进行比对, 比如 account : Account(balance > 100) 这个规则的含义就是在Fact中找到类型为A ...

  7. RabbitMQ 的消息持久化与 Spring AMQP 的实现剖析

    文章目录 1. 原生的实现方式 2. Spring AMQP 的实现方式   要从奔溃的 RabbitMQ 中恢复的消息,我们需要做消息持久化.如果消息要从 RabbitMQ 奔溃中恢复,那么必须满足 ...

  8. Oracle使用中的常规操作总结

    写一篇在使用Oracle过程中一些常用的操作,以便于忘记的时候查看 一.创建用户和给用户赋予权限 create user 用户名 identified by 密码; --12c一下版本 create ...

  9. Golang逃逸分析

    Golang逃逸分析 介绍逃逸分析的概念,go怎么开启逃逸分析的log. 以下资料来自互联网,有错误之处,请一定告之. sheepbao 2017.06.10 什么是逃逸分析 wiki上的定义 In ...

  10. Python3.x--33个保留字

    查询方法也在下图: