spring中7种事务传播行为测试demo

4 篇文章 1 订阅

spring中的事务传播机制的demo、事务7种传播行为Demo、@Transation常见面试题、@Transactional注解啥时候下失效?、一个service调用另一个service的事务传播行为、serviceA调用serviceB,serviceA中加了Transaction,serviceB没加,如果a、b有一个报错会怎样、@transtational可以加在私有方法中吗?、spring的事务传播行为,同一个类中,a事务正常,b方法报错了,这个整体是回滚还是提交、@Transaction详解、@Transaction中调用方报错,被调用方会回滚事务嘛、service调用另一个service报错了,两个都会回滚吗

文章目录


前言

之前部门内部开会,复盘线上事故时,同事有个bug,排查的时候发现是由于对spring中的事务传播行为理解的不清晰导致的。因此写篇文章,分享出来的同时,也能让我再次熟悉一下事务传播。本人水平有限,如有误导,欢迎斧正,一起学习,共同进步!


一、什么是事务?

事务的ACID四大特性,咱们这次主要针对于A原子性。原子性,简单的说,我要做两件事,要么同时成功,要么同时失败,不能是一件成功一件失败。如果第一件成功了,第二件失败了,那我会让第一件也失败,这就是事务。其实能搜到这篇文章的,大家应该都知道,直接看demo吧。

二、demo地址以及前置工作

1、git地址

项目代码路径是:git地址有需求的可以自行下载。

2、7种传播行为

在这里插入图片描述

  • REQUIRED:spring默认的事务传播级别,当前存在事务,则加入该事务,当前不存在事务,则新建一个事务。

  • REQUIRED_NEW:如果当前没有事务,则新建一个事务。如果当前存在事务,则新建一个事务。(不管原先有没有,都会新建事务)。新老事务相互独立,外部事务抛出异常回滚不会影响内部事务的正常提交。

  • SUPPORTS:如果调用方(A)有事务时,被调用方(B)加入到A的事务中;如果A没有事务,则以非事务的方式运行。

  • NOT_SUPPORTED:以非事务的方式运行,如果有事务,则将当前事务挂起。

  • MANDATORY:强制事务执行,如果当前存在事务,则加入该事务;如果当前不存在事务,则抛出异常。

  • NEVER:以非事务方式运行,如果不存在事务,以非事务方式运行。如果存在事务,则抛出异常。

  • NESTED:NESTED是说,如果当前存在事务,则嵌套在当前事务中执行;如果当前没有事务,则新建一个事务。

3、调用顺序

当A方法调用B方法时,流程如下。下面demo中的A方法指UserService,B方法指TeacherService , 也就是调用方指A,被调用方指B。
在这里插入图片描述

三、demo代码。

1.Transactional(propagation = Propagation.REQUIRED)

spring默认的事务传播级别,当前存在事务,则加入该事务,当前不存在事务,则新建一个事务。

1.1、外部(调用方,A方法)无事务

测试:外部无事务,内部有事务,内部事务是required。外部抛异常。
结论:外层方法没有事务时,即使外层方法报错,两次提交都不会回滚。
因为外层没有事务时,insert(user的insert)会自动提交(由于连接的是mysql数据库,所以会自动提交),所以你insert之后的异常,是没有用的,已经提交了。
而Teacher的insert,是由于teacher成功了,所以teacher他也提交了(由于连接的是mysql数据库,所以会自动提交),导致的现象就是,即使方法报错了,也两次都提交了。

/**
 * @author: ztl
 * @date: 2022/12/27 11:18
 * @desc: 测试:外部无事务,内部有事务,内部事务是required。外部抛异常。
 *  结论:外部、内部都不会回滚。
 *  测试User(A)没有事务,teacher(B)会自己创建事务。
 *  这里没有开启事务,teacherService.insert(); 会自己开启事务。
 *          teacherService.insert()方法上有@Transactional(propagation = Propagation.REQUIRED)
 *  而且俩个添加操作都会成功。因为userMapper.insert(user);是自动提交的(因为这个insert0方法没开启事务,又是mysql,所以自动提交了)
 *
 *      因为调用方的A方法(insert0)没有事务,所以 userMapper.insert(user);会自动提交,因为没有事务,且是mysql默认级别是可重复读,自动提交
 *      而teacherService.insert();虽然加了注解,开启了事务,但是由于他成功了,所以他也提交了。
 *      所以尽管insert0方法最后报错了,但是由于这俩都各自提交了,所以并不会回滚。
 */
@Override
public void insert0(){

    teacherService.insert();

    User user = new User(1,"user_"+System.currentTimeMillis());
    userMapper.insert(user);

    int i = 1/0; // 这里抛出异常,两个提交操作都不会回滚(会成功)
}


============= teacherService.insert();方法如下: ==========
 
    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public int insert() {
        Teacher teacher = new Teacher(1,"teacher_"+System.currentTimeMillis());
        return teacherMapper.insert(teacher);
    }

1.2、外部(调用方,A方法)有事务

测试:外部有事务,是required,内部有事务,是required。外部抛异常。
结论:外部、内部都会回滚事务。
当外层的,调用方的方法(A方法)有事务时,被调用方(B方法)即使有事务,也会加入到当前的事务中,所以他们是同一个事务,都是insert1方法的事务,所以当有异常时,两个都会回滚。

/**
 * @author: ztl
 * @date: 2022/12/28 10:20
 * @desc: 测试:外部有事务,是required,内部有事务,是required。外部抛异常。
 *   结论:外部、内部都会回滚事务。
 *   A(调用方,也就是insert1)方法加了@Transactional注解,会开启事务。
 *   teacherService.insert(); 也加了@Transactional注解,也会开启事务
 *   由于user的insert1方法有事务了,而且teacherService.insert()的类型是REQUIRED,
 *          所以teacher的事务会加入到user的事务(insert1方法的事务),就是同一个事务了。
 *   同一个事务中,有异常,所以两个都会回滚
 *
 */
@Transactional(propagation = Propagation.REQUIRED)
@Override
public void insert1(){
    teacherService.insert();

    User user = new User(2,"user_"+System.currentTimeMillis());
    userMapper.insert(user);

    int i = 1/0; // 这里抛出异常,两个提交操作都会回滚(不会成功)
}

============= teacherService.insert();方法如下: ==========
@Override
@Transactional(propagation = Propagation.REQUIRED)
public int insert() {
    Teacher teacher = new Teacher(1,"teacher_"+System.currentTimeMillis());
    return teacherMapper.insert(teacher);
}

2、Transactional(propagation = Propagation.REQUIRED_NEW)

2.1、外部(调用方,A方法)抛异常

REQUIRED_NEW的意思是,如果当前没有事务,则新建一个事务。如果当前存在事务,则新建一个事务。(不管原先有没有,都会新建事务)。新老事务相互独立,外部事务抛出异常回滚不会影响内部事务的正常提交。

测试:外部有事务,是required,内部有事务,是required_new。外部抛异常
结论:外层insert2报错,teacherService.insert2不会回滚,userMapper.insert会回滚。
内部事务是required_new的传播行为时,会新建一个事务,即使外部事务回滚也不会影响内部事务的提交。外层方法开启事务时,即使外层方法报错,内部的事务不会回滚,依然会提交。
注意:外部方法是 required,内部方法是 required_new

/**
 * @author: ztl
 * @date: 2022/12/27 11:18
 * @desc: 测试:外部有事务,是required,内部有事务,是required_new。外部抛异常
 *  结论:外层insert2报错,teacherService.insert2不会回滚,userMapper.insert会回滚。
 *   teacherService.insert2()的传播行为是propagation = Propagation.REQUIRES_NEW
 *   由于调用方(A,也就是insert2)加了Transactional,开启了事务,被调用方(B,也就是teacherService.insert2)也开启了事务,
 *          并且B的传播行为是REQUIRED_NEW,不管当前有没有事务,都会新建一个事务,并且外部事务回滚不会影响内部事务提交。
 *          所以teacherService.insert2()新建了一个新事务,并且新事务成功,所以会提交。
 *          即使外部user的insert2方法异常了,也不影响内部teacher的insert2方法的提交
 *   总的流程如下:
 *      user的insert2开启了一个事务。
 *      teacher的insert2开启了一个事务,并且将user的insert2的事务挂起
 *      user的insert2的事务运行完毕,再恢复user的insert2的事务,
 *      两个事务的提交和回滚都在各自的事务中完成。
 */
@Transactional(propagation = Propagation.REQUIRED)
@Override
public void insert2(){
    teacherService.insert2();

    User user = new User(1,"user_"+System.currentTimeMillis());
    userMapper.insert(user);

    int i = 1/0; // 这里抛出异常,两个提交操作都会回滚(不会成功)
}


============= teacherService.insert();方法如下: ==========

@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public int insert2() {
    Teacher teacher = new Teacher(1,"teacher_"+System.currentTimeMillis());
    return teacherMapper.insert(teacher);
}

2.2、内部(被调用方,B方法)抛异常

测试:外部有事务,是required。内部有事务,是required_new,在内部抛出异常。
结论:外部、内部都会回滚。
虽然是俩事务,但是内部事务异常回滚了,内部会将异常抛出去,抛到外层,相当于外部调用那行直接new throw Exception,所以会回滚。

/**
 * @author: ztl
 * @date: 2022/12/27 11:18
 * @desc: 测试:外部有事务,是required。内部有事务,是required_new,在内部抛出异常。
 *  结论:外部、内部都会回滚。虽然是俩事务,但是内部事务异常回滚了,内部会将异常抛出去,相当于外部也有异常了。
 *  外部有事务,是required,内部有事务,是required_new.
 *  内部事务开启,将外部事务挂起,内部事务异常,内部事务回滚。并且,内部将异常抛出到外部,
 *  内部事务执行完毕,将外部事务从挂起恢复,外部事务异常(是由内部抛出来的),外部事务回滚。
 *  其实teacherService.insert3()异常,就相当于直接在这行代码下面,写一句:
 *      throw new Exception..,所以,内部、外部都会回滚。
 */
@Transactional(propagation = Propagation.REQUIRED)
@Override
public void insert3(){

    User user = new User(1,"user_"+System.currentTimeMillis());
    userMapper.insert(user);

    // 这个方法会抛出异常,就相当于直接在这个代码的下面写: int i = 1/0,也就相当于直接在这段下面 throw new Exception..
    teacherService.insert3();
}

============= teacherService.insert();方法如下: ==========
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Override
public int insert3() {
    Teacher teacher = new Teacher(1,"teacher_"+System.currentTimeMillis());
    int insert = teacherMapper.insert(teacher);

    int i = 1/0; // 这里抛出异常,内部有异常的话,会将异常抛出去,所以外部也会有异常了。
    return insert;
}

3、Transactional(propagation = Propagation.SUPPORTS

如果调用方(A)有事务时,被调用方(B)加入到A的事务中;如果A没有事务,则以非事务的方式运行。

3.1、外部(调用方,A方法)有事务

测试:外部有事务REQUIRED,内部有事务SUPPORTS,外部抛异常
结论:两个都回滚。

/**
 * @author: ztl
 * @date: 2022/12/29 16:54
 * @desc: 外部有事务REQUIRED,内部有事务SUPPORTS,外部抛异常
 *  结论:两个都回滚。
 *  SUPPORTS是,外部有事务,则加入该事务,外部无事务,则以非事务方式运行
 *  外部方法user.insert4 有Transactional注解,则开启了事务,属于外部有事务
 *  内部teacherService.insert4()是supports,会直接加入到外部事务,是同一个事务了。
 *  同一个事务,发生异常,回滚,所以俩都回滚了。
 */
@Transactional(propagation = Propagation.REQUIRED)
@Override
public void insert4(){

    teacherService.insert4();

    User user = new User(1,"user_"+System.currentTimeMillis());
    userMapper.insert(user);

    int i = 1/0; // 这里抛出异常,
}

============= teacherService.insert();方法如下: ==========

@Transactional(propagation = Propagation.SUPPORTS)
@Override
public int insert4() {
    Teacher teacher = new Teacher(1,"teacher_"+System.currentTimeMillis());
    int insert = teacherMapper.insert(teacher);

    return insert;
}

3.2、外部(调用方,A方法)无事务

测试:外部无事务,内部有事务且为supports类型。外部抛出异常
结论:两者都不会回滚,都提交了。

/**
 * @author: ztl
 * @date: 2022/12/29 17:08
 * @desc: 外部无事务,内部有事务且为supports类型。外部抛出异常
 *  结论:两者都不会回滚,都提交了。
 *  supports是,外部有事务,则加入该事务;外部无事务,则以非事务的方式运行。
 *  外部无事务(user的insert5方法没加Transactional),则以非事务运行。
 *  teacherService.insert5以非事务,又是mysql数据库,mysql是自动提交,insert5方法没报错,则提交了
 *  user。insert也一样,insert方法没报错,则自动提交了。
 *  都提交完事务了才报错,跟他俩就没关系了,因为已经提交了,所以俩都不会回滚。
 */
@Override
public void insert5(){

    teacherService.insert5();

    User user = new User(1,"user_"+System.currentTimeMillis());
    userMapper.insert(user);

    int i = 1/0; // 这里抛出异常,
}

============= teacherService.insert();方法如下: ==========

@Transactional(propagation = Propagation.SUPPORTS)
@Override
public int insert5() {
    Teacher teacher = new Teacher(1,"teacher_"+System.currentTimeMillis());
    int insert = teacherMapper.insert(teacher);

    return insert;
}

4、@Transactional(propagation = Propagation.NOT_SUPPORTED

以非事务的方式运行,如果有事务,则将当前事务挂起。

4.1、外部(调用方,A方法)有事务

测试:外部有事务,内部有事务,内部事务NOT_SUPPORTED,外部抛异常
结论:外部(user)insert回滚,内部(teacher)insert提交。

/**
 * @author: ztl
 * @date: 2023/01/03 10:35
 * @desc: 外部有事务,内部有事务,内部事务NOT_SUPPORTED,外部抛异常
 *  结论:外部(user)insert回滚,内部(teacher)insert提交。
 *   NOT_SUPPORTED,不支持事务,如果有事务,则挂起该事务并以非事务运行。
 *   外部user。insert加了注解Transactional,开启了事务。
 *   内部teacher加了注解,并且是not_supported,
 *   内部挂起外部的事务,并以非事务方式运行,非事务,mysql数据库,自动提交了
 *   内部运行完毕,已经提交的前提下,恢复外部事务,外部事务遇到异常,外部事务回滚。
 */
@Transactional(propagation = Propagation.REQUIRED)
@Override
public void insert6(){

    teacherService.insert6();

    User user = new User(1,"user_"+System.currentTimeMillis());
    userMapper.insert(user);

    int i = 1/0; // 这里抛出异常,
}

============= teacherService.insert();方法如下: ==========

// 外部调用方有事务
@Transactional(propagation = Propagation.NOT_SUPPORTED)
@Override
public int insert6() {
    Teacher teacher = new Teacher(1,"teacher_"+System.currentTimeMillis());
    int insert = teacherMapper.insert(teacher);

    return insert;
}

4.2、外部(调用方,A方法)无事务

测试:外部无事务,内部有事务,内部not_supported,外部抛出异常
结论:两个都提交

/**
 * @author: ztl
 * @date: 2022/12/27 11:18
 * @desc: 外部无事务,内部有事务,内部not_supported,外部抛出异常
 *  结论:两个都提交。
 *  not_supported,以非事务运行,如果有事务则挂起,以非事务运行。
 *  外部无事务,内部not_supports,都是没事务,都自动提交,
 *  因为mysql是自动提交的,都已经提交完再抛出异常,所以俩都提交。
 */
@Override
public void insert7(){

    teacherService.insert7();

    User user = new User(1,"user_"+System.currentTimeMillis());
    userMapper.insert(user);

    int i = 1/0; // 这里抛出异常,
}


============= teacherService.insert();方法如下: ==========

// 外部调用方无事务
@Transactional(propagation = Propagation.NOT_SUPPORTED)
@Override
public int insert7() {
    Teacher teacher = new Teacher(1,"teacher_"+System.currentTimeMillis());
    int insert = teacherMapper.insert(teacher);

    return insert;
}

5、Transactional(propagation = Propagation.MANDATORY)

强制事务执行,如果当前存在事务,则加入该事务;如果当前不存在事务,则抛出异常

5.1、外部(调用方,A方法)有事务

测试:外部有事务,内部有事务,内部是MANDATORY,外部抛出异常
结论:内部、外部都回滚。(抛出的是外部的异常by zero)

/**
 * @author: ztl
 * @date: 2023/01/03 14:36
 * @desc: 外部有事务,内部有事务,内部是MANDATORY,外部抛出异常
 *  结论:内部、外部都回滚。(抛出的是外部的异常by zero)
 *  mandatory是,强制事务执行,如果有事务,加入该事务;如果没有事务,则抛出异常。
 *  内部事务是MANDATORY,如果外部有事务,则内部事务加入到外部事务;
 *      如果外部没事务,内部事务抛出异常。
 *  外部加了Transactional注解,开启了事务,内部的(teacher)事务会加入,
 *  加入了以后,是一个事务,同一个事务内,出现异常,回滚,两个都回滚。
 */
@Transactional(propagation = Propagation.REQUIRED)
@Override
public void insert8(){

    teacherService.insert8();

    User user = new User(1,"user_"+System.currentTimeMillis());
    userMapper.insert(user);

    int i = 1/0; // 这里抛出异常,
}

============= teacherService.insert();方法如下: ==========

// 外部调用方无事务、有事务
@Transactional(propagation = Propagation.MANDATORY)
@Override
public int insert8() {
    Teacher teacher = new Teacher(1,"teacher_"+System.currentTimeMillis());
    int insert = teacherMapper.insert(teacher);

    return insert;
}

5.2、外部(调用方,B方法)无事务

测试:外部无事务,内部有事务,mandatory,外部抛异常,
结论:外部、内部全部回滚(抛出的是内部的异常,no existing transaction for ‘mandatory’)

/**
 * @author: ztl
 * @date: 2023/01/03 11:18
 * @desc: 外部无事务,内部有事务,mandatory,外部抛异常,
 *  结论:外部、内部全部回滚(抛出的是内部的异常,no existing transaction for 'mandatory')
 *  mandatory是,强制事务执行,若当前存在事务,则加入该事务;若当前不存在事务,则抛出异常。
 *  外部有事务,则加入该事务,外部没事务,直接抛出异常。
 *  所以即使你 1/0 了,报错也不是 by zero,而是:
 *          org.springframework.transaction.IllegalTransactionStateException: No existing transaction found for transaction marked with propagation 'mandatory'
 *  由于teacher(内部)抛出了异常,所以内部回滚。内部抛出去了,异常到外部了,
 *  外部有异常,所以外部也回滚了。
 */
@Override
public void insert9(){

    teacherService.insert8();

    User user = new User(1,"user_"+System.currentTimeMillis());
    userMapper.insert(user);

    int i = 1/0; // 这里抛出异常,
}

============= teacherService.insert();方法如下: ==========

// 外部调用方无事务、有事务
@Transactional(propagation = Propagation.MANDATORY)
@Override
public int insert8() {
    Teacher teacher = new Teacher(1,"teacher_"+System.currentTimeMillis());
    int insert = teacherMapper.insert(teacher);

    return insert;
}

6、Transactional(propagation = Propagation.NEVER)

以非事务方式运行,如果不存在事务,以非事务方式运行。如果存在事务,则抛出异常。

6.1、外部(调用方,A方法)有事务

测试:外部有事务,内部有事务,内部为never,外部抛出异常
结论:内部、外部都回滚。不会提交。(异常是Existing transaction for ‘never’)

/**
 * @author: ztl
 * @date: 2023/01/03 15:08
 * @desc: 外部有事务,内部有事务,内部为never,外部抛出异常
 *  结论:内部、外部都回滚。不会提交。(异常是Existing transaction for 'never')
 *  never是,以非事务方式运行,如果当前不存在事务,以非事务运行;如果当前存在事务,则抛出异常。异常是:
 *          org.springframework.transaction.IllegalTransactionStateException: Existing transaction found for transaction marked with propagation 'never'
 *  外部加了transaction注解,外部有事务,内部是never,never如果存在事务,则抛出异常,内部抛出异常,内部回滚
 *  内部抛出的异常到外部了,外部遇到异常,直接不往下执行了,user的insert不会进行。
 */
@Transactional(propagation = Propagation.REQUIRED)
@Override
public void insert11(){

    teacherService.insert10();

    User user = new User(1,"user_"+System.currentTimeMillis());
    userMapper.insert(user);

    int i = 1/0; // 这里抛出异常,
}

============= teacherService.insert();方法如下: ==========

// 外部调用方无事务、有事务
@Transactional(propagation = Propagation.NEVER)
@Override
public int insert10() {
    Teacher teacher = new Teacher(1,"teacher_"+System.currentTimeMillis());
    int insert = teacherMapper.insert(teacher);

    return insert;
}

6.2、外部(调用方,A方法)无事务

测试:外部无事务,内部有事务,内部为never,外部抛出异常
结论:两个都不回滚,都提交。(异常是by zero)

/**
 * @author: ztl
 * @date: 2023/01/03 15:02
 * @desc: 外部无事务,内部有事务,内部为never,外部抛出异常
 *  结论:两个都不回滚,都提交。(异常是by zero)
 *  never是,以非事务方式运行,如果当前存在事务,则抛出异常,如果当前不存在事务,以非事务方式运行。
 *  外部没加transaction,所以外部无事务,内部为never,所以以非事务方式运行。
 *  非事务运行,内部(teacher)成功,直接提交。
 *  非事务运行,外部(user)因为是连接的mysql数据库,自动提交,所以也提交,提交完了才报错,不影响,所以不回滚。
 */
@Override
public void insert10(){

    teacherService.insert10();

    User user = new User(1,"user_"+System.currentTimeMillis());
    userMapper.insert(user);

    int i = 1/0; // 这里抛出异常,
}

============= teacherService.insert();方法如下: ==========

// 外部调用方无事务、有事务
@Transactional(propagation = Propagation.NEVER)
@Override
public int insert10() {
    Teacher teacher = new Teacher(1,"teacher_"+System.currentTimeMillis());
    int insert = teacherMapper.insert(teacher);

    return insert;
}

7、Transactional(propagation = Propagation.NESTED)

NESTED是说,如果当前存在事务,则嵌套在当前事务中执行;如果当前没有事务,则新建一个事务。

7.1、外部(调用方,A方法)抛异常

测试:外部有事务,内部有事务,内部NESTED,外部抛异常
结论:外部、内部、全部回滚(外部事务异常,会导致嵌套内事务同步回滚)。

/**
 * @author: ztl
 * @date: 2023/01/09 15:22
 * @desc: 外部有事务,内部有事务,内部NESTED,外部抛异常
 *  结论:外部、内部、全部回滚(外部事务异常,会导致嵌套内事务同步回滚)。
 *  NESTED是说,如果当前存在事务,则嵌套在当前事务中执行;如果当前没有事务,则新建一个事务
 *  外部加了Transaction,外部有事务,内部是nested,内部事务会嵌套在外部事务中。
 *  内部teacher成功,提交了,外部事务user失败了,会导致内部的teacher同步回滚。
 */
@Transactional(propagation = Propagation.REQUIRED)
@Override
public void insert12(){

    try {
        teacherService.insert12();
    }catch (Exception e){
        e.printStackTrace();
        System.out.printf("exception:"+e);
    }

    User user = new User(1,"user_"+System.currentTimeMillis());
    userMapper.insert(user);

    int i = 1/0; // 这里抛出异常,
}

============= teacherService.insert();方法如下: ==========

@Transactional(propagation = Propagation.NESTED)
@Override
public int insert12() {
    Teacher teacher = new Teacher(1,"teacher_"+System.currentTimeMillis());
    int insert = teacherMapper.insert(teacher);

    return insert;
}

7.2、内部(被调用方,B方法)抛异常

测试:外部有事务required,内部有事务nested,内部抛异常
结论:外部提交,内部回滚

/**
 * @author: ztl
 * @date: 2023/01/09 15:41
 * @desc: 外部有事务required,内部有事务nested,内部抛异常
 *  结论:外部提交,内部回滚
 *  NESTED是说,如果当前存在事务,则嵌套在当前事务中执行;如果当前没有事务,则新建一个事务
 *  说明外层的异常可以影响到内层的事务,内层的事务影响不到外部的事务。
 *  teacher异常,被回滚了,teacher抛到外层user这层的,被捕获了,没异常,提交
 */
@Transactional(propagation = Propagation.REQUIRED)
@Override
public void insert13(){

    try {
        teacherService.insert13();
    }catch (Exception e){
        e.printStackTrace();
        System.out.printf("exception:"+e);
        throw new RuntimeException("123");
    }

    User user = new User(1,"user_"+System.currentTimeMillis());
    userMapper.insert(user);
}

============= teacherService.insert();方法如下: ==========

// 外部调用方无事务、有事务
@Transactional(propagation = Propagation.NESTED)
@Override
public int insert13() {
    Teacher teacher = new Teacher(1,"teacher_"+System.currentTimeMillis());
    int insert = teacherMapper.insert(teacher);

    int i = 1/0; // 这里抛出异常,
    return insert;
}

7.3、内部(被调用方,B方法)、外部都不抛异常

测试:外部有事务required,内部有事务nested,内部外部都不抛异常
结论:内部、外部都提交

@Transactional(propagation = Propagation.REQUIRED)
@Override
public void insert14(){

    teacherService.insert14();

    User user = new User(1,"user_"+System.currentTimeMillis());
    userMapper.insert(user);

}

============= teacherService.insert();方法如下: ==========

@Transactional(propagation = Propagation.NESTED)
@Override
public int insert14() {
    Teacher teacher = new Teacher(1,"teacher_"+System.currentTimeMillis());
    int insert = teacherMapper.insert(teacher);

    return insert;
}

7.4、requires_new和nested区别测试demo

7.4.1、外部(调用方,A方法)抛异常

测试:外部有事务required,内部有事务,分别为nested、requires_new。外部抛异常
结论当为nested时,外部异常,内部、外部都回滚。当为requires_new时,外部异常。外部回滚,内部提交

/**
     * @author: ztl
     * @date: 2022/12/27 11:18
     * @desc: 测试nested和requires_new区别:
     *  外部有事务required,内部有事务,分别为nested、requires_new。外部抛异常,
     *  结论:当为nested时,外部异常,内部、外部都回滚。当为requires_new时,外部异常。外部回滚,内部提交
     */
    @Transactional(propagation = Propagation.REQUIRED)
    @Override
    public void insert17(){

        try {
//            teacherService.insert17(); // 外部required,内部nested,外部抛异常,内部、外部都回滚
            teacherService.insert18(); // 外部required,内部requires_new,外部抛异常,外部回滚,内部提交
        }catch (Exception e){
            e.printStackTrace();
            System.out.printf("exception:"+e);
        }

        User user = new User(1,"user_"+System.currentTimeMillis());
        userMapper.insert(user);
        int i = 1/0; // 这里抛出异常,
    }
    
============= teacherService.insert();方法如下: ==========
        
// 测试nested和requires_new的区别,外部抛异常
@Transactional(propagation = Propagation.NESTED)
@Override
public int insert17() {
    Teacher teacher = new Teacher(1,"teacher_"+System.currentTimeMillis());
    int insert = teacherMapper.insert(teacher);


    return insert;
}

// 测试nested和requires_new的区别,外部抛异常
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Override
public int insert18() {
    Teacher teacher = new Teacher(1,"teacher_"+System.currentTimeMillis());
    int insert = teacherMapper.insert(teacher);

    return insert;
}
7.4.2、内部(被调用方,B方法)抛异常

测试:外部有事务required,内部有事务,内部事务分别为nested、requires_new。内部抛异常,外部catch住。
结论:内部都回滚,外部都提交。nested、requires_new在内部有异常外部无异常时,这俩一样

/**
     * @author: ztl
     * @date: 2023/01/16 16:26
     * @desc: 测试nested和requires_new区别:
     *  外部有事务required,内部有事务,分别为nested、requires_new。内部抛异常,外部catch住,
     *  结论:内部都回滚,外部都提交。nested、requires_new在内部有异常外部无异常时,这俩一样
     */
    @Transactional(propagation = Propagation.REQUIRED)
    @Override
    public void insert15(){

        try {
//            teacherService.insert15(); // 外部required,内部nested,内部抛异常,外部提交,内部回滚
            teacherService.insert16(); // 外部required,内部requires_new,内部抛异常,外部提交,内部回滚
        }catch (Exception e){
            e.printStackTrace();
            System.out.printf("exception:"+e);
        }

        User user = new User(1,"user_"+System.currentTimeMillis());
        userMapper.insert(user);
    }
    
    
    ============= teacherService.insert();方法如下: ==========
    
    
// 测试nested和requires_new的区别,内部抛异常
@Transactional(propagation = Propagation.NESTED)
@Override
public int insert15() {
    Teacher teacher = new Teacher(1,"teacher_"+System.currentTimeMillis());
    int insert = teacherMapper.insert(teacher);

    int i = 1/0; // 这里抛出异常,
    return insert;
}

// 测试nested和requires_new的区别,内部抛异常
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Override
public int insert16() {
    Teacher teacher = new Teacher(1,"teacher_"+System.currentTimeMillis());
    int insert = teacherMapper.insert(teacher);

    int i = 1/0; // 这里抛出异常,
    return insert;
}

8、注:我上面A、B是在两个类中,同一个类中A、B方法不会回滚和提交!!!)

四、日常开发中常用的方式

1、无事务场景

测试:内部、外部都没事务,外部抛异常,
结论:内部外部都提交
由于连接的是mysql数据库,自动提交,是提交完以后才报的错,已经提交过了,所以不会回滚,会提交。

/**
 * @author: ztl
 * @date: 2023/01/16 16:46
 * @desc: 内部、外部都没事务,外部异常,内部外部都提交
 *  因为连接的是mysql数据库,会自动提交,都已经提交完了才报错,所以两个都不会回滚
 */
@Override
public void insert19(){

    teacherService.insert19();
    User user = new User(1,"user_"+System.currentTimeMillis());
    userMapper.insert(user);
    int i = 1/0; // 这里抛出异常,
}

============= teacherService.insert();方法如下: ==========

@Override
public int insert19() {
    Teacher teacher = new Teacher(1,"teacher_"+System.currentTimeMillis());
    int insert = teacherMapper.insert(teacher);

    return insert;
}

2、(最常用)外部@Transaction,内部无事务(无注解)

外部加了@Transaction注解,所以开启了事务,未指定propagation,即是默认required。外部加了注解,开启了事务,required,内部无事务。
不管是外部、内部,任何一个地方有异常,则内部、外部全部回滚
测试:外部抛异常
结论:内部、外部都回滚
测试:内部抛异常
结论:内部、外部都回滚

/**
     * @author: ztl
     * @date: 2023/01/16 16:46
     * @desc: 外部有事务,required,内部无事务,
     *  外部抛异常:内部、外部都回滚。
     *  内部抛异常:
     */
    @Transactional
    @Override
    public void insert20(){

        teacherService.insert19(); // 外部异常,内部无事务,内部、外部都要回滚。
//        teacherService.insert20(); // 内部异常,内部无事务,内部、外部都要回滚
        User user = new User(1,"user_"+System.currentTimeMillis());
        userMapper.insert(user);
//        int i = 1/0; // 这里抛出异常,
    }
    
    @Override
public int insert19() {
    Teacher teacher = new Teacher(1,"teacher_"+System.currentTimeMillis());
    int insert = teacherMapper.insert(teacher);

    return insert;
}

@Override
public int insert20() {
    Teacher teacher = new Teacher(1,"teacher_"+System.currentTimeMillis());
    int insert = teacherMapper.insert(teacher);
    int i = 1/0; // 这里抛出异常,
    return insert;
}

五、Transaction常见面试题

1、spring的事务传播行为,同一个类中,a事务正常,b方法报错了,这个整体是回滚还是提交

在一个Service内部,事务方法之间的嵌套调用,普通方法和事务方法之间的嵌套调用,都不会开启新的事务.是因为spring采用动态代理机制来实现事务控制,而动态代理最终都是要调用原始对象的,而原始对象在去调用方法时,是不会再触发代理了!
如果spring的同一个类中,a方法有事务,b方法没有事务,那么a,b之间,事务没法传播,都不会自动的传播事务,需要自己手动提交或回滚。
不管循环嵌套了多少个事务,只要最上层的类有事务控制,那么不管是哪个方法调用异常,都会回滚(前提是不是同一个类)。
如果一个有事务的方法,去调用了没有事务的方法,会走事务;如果一个没有事务的方法,调用了带有事务的方法,是不会有事务的。
因为@Transational注解是,给当前类生成一个子类,然后在子类执行响应的业务逻辑前,先加上事务,如果执行结束,就提交或者回滚。而如果同一个类的话,事务方法是在当前类的子类(代理类)里面执行的,普通方法是在当前类中执行的,都不是一个类,肯定没法统一的事务处理。
要解决的话:可以在类上加@Transactional而不是在类的方法中加上,或者你在嵌套调用的时候,加一个try,catch,用来捕获,并做处理。
spring的默认事务规则(7种传播行为)是,如果当前存在事务,则加入该事务;如果当前不存在事务,则创建一个新事务。

2、spring的@Transactional注解啥时候下失效?

私有方法、同一个类中、@Transactional 注解,不支持静态方法

3、@transtational可以加在私有方法中吗

方法可见性和@Transactional在使用代理时,应用@Transactional只对具有公共可见性的方法进行注释。如果确实对受保护的、私有的或包可见的方法进行注释,则使用@Transactional注释,不会引发错误,但带注释的方法不会显示已配置的事务设置。如果需要注释非公共方法,请考虑使用AspectJ(见下文)。@Transactional修饰static方法时,会报错
原理: spring
在扫描bean的时候会扫描方法上是否包含@Transactional注解,如果包含,spring会为这个bean动态地生成一个子类(即代理类,proxy),代理类是继承原来那个bean的。此时,当这个有注解的方法被调用的时候,实际上是由代理类来调用的,代理类在调用之前就会启动transaction。然而,如果这个有注解的方法是被同一个类中的其他方法调用的,那么该方法的调用并没有通过代理类,而是直接通过原来的那个bean,所以就不会启动transaction,我们看到的现象就是@Transactional注解无效。


总结

到这里大家应该对spring中的传播行为有了一定的了解了。建议平时开发的时候多测测,这样即使线上环境真出问题了,也会同步失败,不会造成大量的脏数据,也不会有很严重的生产事故。以后有时间了,还会从源码的角度,分析为何@Transaction会起作用。以及何时不会起作用。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值