Spring的@Transactional

说起来,感觉自己挺懒,Spring学到现在经过6个月了,对事务还是一知半解的,直到今天在掘金上看到篇文章

一口气说出6种@Transactional失效的场景,虽然看过之后感觉理解比以前好了点,但还是懵逼状态(很多术语都不知道什么意思,对源代码没信心,只能自己写代码测试结果了)。

我的测试环境:

同类,insert3中调用insert1和insert2两个方法

测试代码如下,使用单元测试

@Service
public class InsertServiceImpl {

    @Autowired
    private InsertTest insertTest;

    @Autowired
    private InsertServiceImpl insertService;

    @Transactional(propagation = Propagation.SUPPORTS, rollbackFor = IOException.class)
    public void insert2(Integer age) throws IOException {
        insertTest.insert(age);
        try {
            throw new IOException();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            throw new IOException();
        }
    }

    @Transactional
     void insert1(Integer age) {
        insertTest.insert(age);
        throw new RuntimeException();
    }

//    @Transactional
    public void insert3(Integer age) throws IOException {
        insertService.insert1(age);
        insertService.insert2(age + 1);
    }
}

 回滚会造成MySQL的自增,id=11时回滚两次,再成功插入数据,id=14

@Transactional默认对RuntimeException和Error会回滚。

@Transactional(rollbackFor = IOException.class)
     public void insert2(Integer age)  {
        insertTest.insert(age);
        try {
            throw new IOException();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public void insert1(Integer age) {
        insertTest.insert(age);
        throw new RuntimeException();
    }

    @Transactional
    public void insert3(Integer age) throws IOException, ReflectiveOperationException {
//   insert2会执行,然后insert1也会执行,insert2因为insert3上的@Transactional回 滚了,而insert1因为NOT_SUPPORTED而挂起事务,不回滚。 
        insertService.insert2(age + 1);
        insertService.insert1(age);
    }

7种失效场景:

我们首先理解原文的第4点

4、同一个类中方法调用,导致@Transactional失效

解释:Spring的@Transactional使用的是aop方式,通过代理完成的。(这句话不能理解就算了,我目前的水平知道大概,但是不能解释清楚)

解决:方法调用不通过直接方法名的形式,而是通过对象.(点)方法名的形式。就如上方代码一样,使用insertService,当前InsertServiceImpl的对象。

解决之后,我们下方都是通过对象名.方法名来做到类似不同类之间的方法调用的事务效果测试。

1、@Transactional 应用在非 public 修饰的方法上

解释:如果哪个方法上有@Transactional注解,但是却是非public访问修饰符,那么可以看做这个@Transactional没有写。

例如:

 1.1、insert3上有@Transactional(不设置任何属性,就一个注解,括号内没有内容)且是public的,同时insert2抛出了IOException并且@Transaction设置rollbackfor=“IOException.class”, 但可惜的是insert2是非public的,那么结果是什么呢?insert3能不能被insert2默认的传播行为同样对IOException回滚?

答案是不行!为什么?因为insert2是非public的,就相当于insert2没有设置@Transactional的,insert2可以看作非事务,既然是非事务,那么自然不能对IOException回滚,也不能对RuntimeException回滚,更不能有默认的传播机制(Propagation.REQUIRED), 如此一来insert3就没有对IOException回滚的能力,insert2抛出异常,程序结束,不回滚。

那么如果保持insert3不变(只有@Transaction并且是public), 那么若是此时insert2抛出RuntimeException并且@Transaction(空)且非public, 那么此时会回滚吗?

答案是可以!原因:单独的调用insert2方法,当然是不会回滚的(因为非public看做非事务),但是insert3调用的insert2且insert3的@Transaction生效(insert3是public修饰),insert3有对RuntimeException回滚的能力。

1.2、上方是对insert3设置的public, 那么此时我们对insert3解除public修饰,访问修饰符不写(默认的访问修饰符),再进行分类讨论。

提问:insert3设置了@Transactional, 那么怎么判断它有没有事务。

答案:insert3本身没有事务(非public都可看做非事务),事务的判断就交由insert3方法体内调用的insert1和insert2方法。在insert3方法体内,insert1和insert2的事务不会互相干扰(他们之间没有相互调用的关系,传播机制可以无视),如果insert1是非public那么insert1就是非事务,执行到insert1()的时候,insert1()它自己抛出任何异常都不会回滚,但如果是public则具体看insert1的@Transactional的设置,对insert1进行判断。同样,若是执行到了insert2(), 也是这么判断insert2(), 如果insert2回滚了,那么只会回滚insert2()自己,它不但能对在insert2()之前的insert1()做出任何干扰。(可以看作因为insert1和insert2是前后关系,并不是像insert3和insert1一样的父子关系)。

此时,非public方法的@Transactional不生效就告一段落。保持着怀疑心的读者们可以自己去谢谢代码测试。

 简单来说就是:

insert3如果有@Transactional但是是非public的,那么insert3就和没有@Transactional一样。这个时候就是看在insert3中被调用的方法有没有@Transactional,此时事务就和insert3()无关了。

被调用的方法,例如insert1,如果他有@Transactional,再看insert1是不是public的,如果非public,那么insert1()就和没有@Transactional一样。

---------------------------

2、@Transactional 注解属性 propagation 设置错误

 2.1、TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。

  解释:@Transactional(propagation=prapagation.SUPPORTS) 可以看作没有@Transactional注解。下方主要是解释上面这句话的“当前”是指什么,“加入”又该怎么理解。

例如:

insert1()设置了@Transactional(propagation=Prapagation.SUPPORTS)后,insert1()自身没有事务了(这句话主要解释都加上@Transactional了吗,怎么会没有事务——“如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行”,前面的这句话不是废话吗?就是和非public修饰一样,它有@Transactional修饰,但是实际上它却又没有事务。嗯……看不懂我表达的请无视,主要是我自己前面这里理解错了,特地说明下。估计有和我相同疑惑的人应该懂我说什么……)。

那么此时的“当前”指的是调用insert1()的方法,而不是指insert1()自身。insert3()调用了insert1(), 那么判断insert3()是否有事务,insert3()有事务,那么insert1()的事务"加入"insert3()可以看作insert1()恢复了自身的@Transactional,此时propagation=Prapagation.SUPPORTS对它的影响已经没了。至于为什么我理解成恢复而不是insert3拥有了insert1()的rollbackfor。

原因:设置insert3()空白的@Transactional,先调用insert2后调用insert1,insert2抛出IOException但是不设置事务对IOException回滚,insert1设置对IOException回滚,但是传播机制为SUPPORTS,可以发现insert2抛出异常后不回滚。

继续验证一下:insert3加入空白的@Transactional,调用insert2, insert2()加上

@Transactional(propagation = Propagation.SUPPORTS,rollbackFor = IOException.class)但是insert2()不throw new IOException, 在insert3()中加上throw new IOException, 发现事务不回滚。可以证明SUPPORTS只是在有事务的情况下恢复了自身的事务。

---------------------

 2.2、TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。

  解释:挂起的意思是:使此时的事务失效,过后恢复正常,他的持续时间和失效范围是设置了propagation=Propagation.NOT_SUPPORTED的方法部分和执行时间。例如:insert3加上@Transactional注解,让insert3()先调用insert1不抛出异常正常运行并设置propagation=Propagation.NOT_SUPPORTED,后调用insert2设置抛出IOException并对IOException回滚,可以发现insert1()插入了数据库,而insert2回滚(也就是说此时insert1()被insert3()调用,而insert3()设置了@Transactional却对insert1的propagation=Propagation.NOT_SUPPORTED不起作用,但对抛出IOException并回滚IOException的insert2()回滚)。

但是,另一种情况,当解除insert2的@Transactional(rollbackfor=IOException.class)事,使insert2()没有事务,却抛出IOException, 发现insert3设置了@Transactional先调用insert2(),insert2有@Transactional(propagation=Propagation.NOT_SUPPORTS),  结果是insert1()和insert2()都没有回滚。

我没有研究源代码,只能怀疑NOT_SUPPORTS挂起了当前事务,指的是调用有该属性的方法的方法,insert3()调用有该注解属性的insert2(), 那么insert3()的事务就被挂起了,而insert1()自己本身有事务,那他就回滚,没有事务就不回滚。

-----------------------

2.3、TransactionDefinition.PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。

抛出的异常是:org.springframework.transaction.IllegalTransactionStateException: Existing transaction found for transaction marked with propagation 'never'

 --------------

5、异常被你的 catch“吃了”导致@Transactional失效

catch会导致不回滚,可以在catch后或finally继续抛出异常——额,多此一举啊。可以试着catch异常后,抛出RuntimeException

6、数据库引擎不支持事务

MySQL使用show engines;show variables like '%storage_engine%';show create table 表名;查看是否是InnoDB引擎,只有它支持事务。

7.评论区的:多数据源失效。多数据源失效,其实不是上面指的transcation注解失的效,而是它本身不支持多数据源。这个应该要使用分布式事务框架或者2pc,3pc等。(额,完全不了解)

---------------------------

额,我自己看了都头晕。做个笔记,等以后看源代码的时候再看看理解是否有误。

原文地址:https://www.cnblogs.com/woyujiezhen/p/12740795.html