55

玩转Spring —— 消失的事务

 收集于2年前 阅读数 72
以下为 快照 页面,建议前往来源网站查看,会有更好的阅读体验。
原文链接: http://bridgeforyou.cn/2018/06/18/The-Missed-Transaction/?amp%3Butm_medium=referral

消失的事务

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

一个没有加@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接口,看看数据库中都有哪些数据:

AfiUBr3.png!web

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

7bQzQfQ.png!web

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

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

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

问题来了,如果我的接口是去调用一个没有加@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);
    }

直觉告诉我,会的。

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

uQjI7vQ.png!web

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

3YzYbiB.png!web

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

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

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

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

图解@Transactional

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

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

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

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

FjUzaeV.png!web

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

F3mqqm3.png!web

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

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

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

aMzamuA.png!web

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

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

VfaeAf3.png!web

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

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

看看代码

最后再来看看代码。

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

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

IjyEJ3Y.png!web

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

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

nqIziui.png!web

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

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

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

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

总结

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

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

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

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

参考

  • 《Spring揭秘》

猜你喜欢

  • 195

    推广 - @qiayue - ## 极课 GeekCourse,一个专做网课返利的公众号。极客时间新课程《玩转 Spring 全家桶》,一站通关 Spring、Spring Boot 与 Spring Cloud。

  • 118

    Spring源码解析之事务篇

  • 43

    分布式事务,一直是实现分布式系统过程中最大的挑战。在只有单个数据源的单服务系统当中,只要这个数据源支持事务,例如大部分关系型数据库,和一些MQ服务,如activeMQ等,我们就可以很容易的实现事务。 本地事物 ...

  • 21

    组内每周都有技术分享,轮着来,人人有份。 上次分享了 Spring的统一事务模型 ,这次聊聊Spring的声明式事务模型。 和上次一样,周五分享完,周末把keynote整理...

  • 12

    理解Spring中的事务

    Spring为事务管理提供了丰富的支持,对于底层不同的事务( 如Java Transaction API (JTA), JDBC, Hibernate, Java Persistence API (JPA), and Java Data Objects (JDO))管理提供了统一的抽象编程模型,而且它的API也非常易于开发者理解...

  • 15

    SpringAOP原理什么是AOP?AOP即面向切面编程,利用AOP可以对业务进行解耦,提高重用性,提高开发效率应用场景:日志记录,性能统计,安全控制,事务处理,异常处理AOP底层实现原理是采用代理实现的Spring事务基本特性:原子性隔离性一致性持久性事务控制分类:编程...

  • 42

    Spring -- 事务

    JDBC/Hibernate/MyBatis DataSource/JTA 核心接口 PlatformTransactionManager DataSourceTransactionManager HibernateTransactionManager ...

  • 31

    浅谈Spring的事务隔离级别与传播性 这篇文章以一个问题开始,如果你知道答案的话就可以跳过不看啦@(o・ェ・)@ Q:在一个批量任务执行的过程中,调用多个子任务时,如果有一些子任务发生异常,只是回滚那些出现异常的任务,而...

  • 10

    背景 本篇文章主要分享压测的(高并发)时候发现的一些问题。之前的两篇文章已经讲述了在高并发的情况下,消息队列和数据库连接池的一些总结和优化,有兴趣的可以在我的公众号中去翻阅。废话不多说,进入正题。

  • 14

    @Transactional Spring 事务源码分析

    @Transactional Spring事务源码分析 在Spring开发中,经常会用到这个注解,但它是怎么起到开启事务的作用的,还一知半解,只知道应该是基于AOP的,今天就来分析一波它的用法和具体实现。 编程式事务和声明式事务

关于极客头条


聚合每日国内外有价值,有趣的链接。

AD