玩转Spring--消失的事务@Transactional
消失的事务
端午节前,组内在讨论一个问题:
一个没有加@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的更多相关文章
- Spring 之注解事务 @Transactional
众所周知的ACID属性: 原子性(atomicity).一致性(consistency).隔离性(isolation)以及持久性(durability).我们无法控制一致性.原子性以及持久性,但可以 ...
- 【@Transactional】Spring 之注解事务 @Transactional
spring 事务注解 默认遇到throw new RuntimeException("...");会回滚 需要捕获的throw new Exception("...&q ...
- Spring 之注解事务 @Transactional(转载)
Spring在TransactionDefinition接口中规定了7种类型的事务传播行为, 它们规定了事务方法和事务方法发生嵌套调用时事务如何进行传播: 事务传播行为类型 事务传播行为类型 说明 P ...
- Spring @Cacheable注解 && 事务@Transactional 在同一个类中的方法调用不生效
@Cacheable 注解在对象内部调用不会生效 代码示例:ProductServiceImpl.java public List<ProductInfoVO> getProductLis ...
- Spring声明式事务@Transactional 详解,事务隔离级别和传播行为
@Transactional注解支持9个属性的设置,这里只讲解其中使用较多的三个属性:readOnly.propagation.isolation.其中propagation属性用来枚举事务的传播行为 ...
- 玩转spring boot——结合JPA事务
接着上篇 一.准备工作 修改pom.xml文件 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi=&q ...
- Spring声明式事务管理基于@Transactional注解
概述:我们已知道Spring声明式事务管理有两种常用的方式,一种是基于tx/aop命名空间的xml配置文件,另一种则是基于@Transactional 注解. 第一种方式我已在上文为大 ...
- 对于spring中事务@Transactional注解的理解
现在spring的配置都喜欢用注解,这边就说下@Transactional 一.如何开启@Transactional支持 要使用@Transactional,spring的配置文件applicatio ...
- 【Spring】16、注解事务 @Transactional
概述 事务管理对于企业应用来说是至关重要的,即使出现异常情况,它也可以保证数据的一致性.Spring Framework对事务管理提供了一致的抽象,其特点如下: 为不同的事务API提供一致的编程模型, ...
随机推荐
- leetcode 111. 二叉树的最小深度
题目描述: 给定一个二叉树,找出其最小深度. 最小深度是从根节点到最近叶子节点的最短路径上的节点数量. 说明: 叶子节点是指没有子节点的节点. 示例: 给定二叉树 [3,9,20,null,null, ...
- git cherry-pick 命令,解决冲突后没有 commit 变更,再次pull 时出错
Git : You have not concluded your cherry-pick (CHERRY_PICK_HEAD exists). 解决方法:(1)使用git status 命令查看当前 ...
- [转帖]kubernetes 常见问题整理
kubernetes 常见问题整理 https://www.cnblogs.com/qingfeng2010/p/10642408.html 使用kubectl 命令报错 报错: [root@k8s- ...
- Linux 常用文件描述
Linux 常用文件描述 /etc/issue 本地登陆显示的信息,本地登录前 /etc/issue.net 网络登陆显示的信息,登录后显示,需要由sshd配置 /etc/motd 常用于通告信息,如 ...
- Spring AOP动态代理实现,解决Spring Boot中无法正常启用JDK动态代理的问题
Spring AOP底层的动态代理实现有两种方式:一种是JDK动态代理,另一种是CGLib动态代理. JDK动态代理 JDK 1.3版本以后提供了动态代理,允许开发者在运行期创建接口的代理实例,而且只 ...
- drools规则语法(一)
1.基本的匹配规则 1.1变量 drools使用匹配的方式对Fact进行比对, 比如 account : Account(balance > 100) 这个规则的含义就是在Fact中找到类型为A ...
- RabbitMQ 的消息持久化与 Spring AMQP 的实现剖析
文章目录 1. 原生的实现方式 2. Spring AMQP 的实现方式 要从奔溃的 RabbitMQ 中恢复的消息,我们需要做消息持久化.如果消息要从 RabbitMQ 奔溃中恢复,那么必须满足 ...
- Oracle使用中的常规操作总结
写一篇在使用Oracle过程中一些常用的操作,以便于忘记的时候查看 一.创建用户和给用户赋予权限 create user 用户名 identified by 密码; --12c一下版本 create ...
- Golang逃逸分析
Golang逃逸分析 介绍逃逸分析的概念,go怎么开启逃逸分析的log. 以下资料来自互联网,有错误之处,请一定告之. sheepbao 2017.06.10 什么是逃逸分析 wiki上的定义 In ...
- Python3.x--33个保留字
查询方法也在下图: