Spring事务传播实测

完整源码地址:https://github.com/crossyourheart/TestTransactionPropagation.git

1.事务传播特性

2.实测

教员说过,实践是检验真理的唯一标准。

2.1 建表

mysql数据库

CREATE TABLE `student` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(45) NOT NULL DEFAULT '',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE `teacher` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(45) NOT NULL DEFAULT '',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

2.2 创建 项目

TestService 调用 StudentService 和 TeacherService 中的代码。 验证事务的传播特性。
在单元测试中,调用 TestService 中的方法。

关键代码:

package com.dawei.transaction.service;

import com.dawei.transaction.mapper.StudentDao;
import com.dawei.transaction.pojo.Student;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

/**
 * @author da wei
 * @description
 * @create 2021/7/15 11:07
 */
@Service
public class StudentServiceImpl implements StudentService {
    @Autowired
    StudentDao studentDao;

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void addRequired(Student student) {
        studentDao.insert(student);
    }


    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void addRequiresNew(Student student) {
        studentDao.insert(student);
    }

    @Override
    @Transactional(propagation = Propagation.NESTED)
    public void addNested(Student student) {
        studentDao.insert(student);
    }

}

package com.dawei.transaction.service;

import com.dawei.transaction.mapper.TeacherDao;
import com.dawei.transaction.pojo.Teacher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

/**
 * @author da wei
 */
@Service
public class TeacherServiceImpl implements TeacherService {
    @Autowired
    TeacherDao teacherDao;

    /**
     * 虽然 @Transactional 注解可以作用于接口、接口方法、类以及类方法上,
     * 但是 Spring 建议不要在接口或者接口方法上使用该注解,因为这只有在使用基于接口的代理时它才会生效。
     * 代理模式有JDK代理 和 cglib代理
     */
    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void addRequired(Teacher teacher) {
        teacherDao.insert(teacher);
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void addRequiredException(Teacher teacher) {
        teacherDao.insert(teacher);
        //此处会抛出一个运行时异常
        int i = 1 / 0;
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void addRequiresNew(Teacher teacher) {
        teacherDao.insert(teacher);
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void addRequiresNewException(Teacher teacher) {
        teacherDao.insert(teacher);
        //此处会抛出一个运行时异常
        int i = 1 / 0;
    }

    @Override
    @Transactional(propagation = Propagation.NESTED)
    public void addNested(Teacher teacher) {
        teacherDao.insert(teacher);
    }

    @Override
    @Transactional(propagation = Propagation.NESTED)
    public void addNestedException(Teacher teacher) {
        teacherDao.insert(teacher);
        int i = 1 / 0;
    }
}

2.3测试结论

请查看注释。

package com.dawei.transaction.test;

import com.dawei.transaction.pojo.Student;
import com.dawei.transaction.pojo.Teacher;
import com.dawei.transaction.service.StudentService;
import com.dawei.transaction.service.TeacherService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

/**
 * @author da wei
 */
@Service
public class TestServiceImpl implements TestService {

    @Autowired
    StudentService studentService;
    @Autowired
    TeacherService teacherService;

    /*-----------------------------1.  测试   propagation = Propagation.REQUIRED---------------------------------------------------------------------------------*/

    /**
     * 1.1  外围没有开启事务
     */

    //1.1.1  在单元测试中调用该方法。 测试结果: 外部未开启事务,两个添加方法在自己的事务中独立运行。
    //外部的异常不影响内部两个方法的插入。
    @Override
    public void noTransactionExceptionRequiredRequired() {
        Student student = new Student();
        student.setName("学生1.1.1");
        studentService.addRequired(student);

        Teacher teacher = new Teacher();
        teacher.setName("教师1.1.1");
        teacherService.addRequired(teacher);
        int i = 1 / 0;
    }

    //1.1.2  在单元测试中调用该方法。 测试结果:学生2 成功插入到数据库。  外围方法没有事务,两个插入的方法独自创建自己
    //的事务,教师添加遇到异常 导致 回滚,学生添加成功。
    @Override
    public void noTransactionRequiredRequiredException() {
        Student student = new Student();
        student.setName("学生1.1.2");
        studentService.addRequired(student);

        Teacher teacher = new Teacher();
        teacher.setName("教师1.1.2");
        teacherService.addRequiredException(teacher);
    }
    //结论:通过上面两个方法我们证明了在外围方法未开启事务的情况下Propagation.REQUIRED修饰的内部方法会新开启自己的事务
    //,且开启的事务相互独立,互不干扰。

    /**
     * 1.2 外围方法开启事务,这个是使用率比较高的场景。
     */

    //1.2.1 在单元测试中调用该方法。  测试结果: 学生和教师均保存失败。 外围方法开启事务,内部方法加入外围方法事务,
    //外围方法回滚,内部方法也要回滚。
    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void transactionExceptionRequiredRequired() {
        Student student = new Student();
        student.setName("学生1.2.1");
        studentService.addRequired(student);

        Teacher teacher = new Teacher();
        teacher.setName("教师1.2.1");
        teacherService.addRequired(teacher);
        int i = 1 / 0;

    }

    //1.2.2 测试结果:全部插入失败。  外围方法开启事务,内部方法加入外围方法事务,内部方法抛出异常回滚,
    //外围方法感知异常致使整体事务回滚。
    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void transactionRequiredRequiredException() {
        Student student = new Student();
        student.setName("学生1.2.2");
        studentService.addRequired(student);

        Teacher teacher = new Teacher();
        teacher.setName("教师1.2.2");
        teacherService.addRequiredException(teacher);

    }

    //1.2.3  全部保存失败。 外围方法开启事务,内部方法加入外围方法事务,内部方法抛出异常回滚,
    //即使方法被catch不被外围方法感知,整个事务依然回滚。
    //这里我特地测试了1.2.1中的方法,捕获 1/0 异常,发现两个均能插入成功。与上面对比,即 捕获普通方法 是不会使事务中止的。
    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void transactionRequiredRequiredExceptionTry() {
        Student student = new Student();
        student.setName("学生1.2.3");
        studentService.addRequired(student);

        Teacher teacher = new Teacher();
        teacher.setName("教师1.2.3");
        try {
            teacherService.addRequiredException(teacher);
        } catch (Exception e) {
            System.out.println("出现异常");
        }
    }
    //结论:以上试验结果我们证明在外围方法开启事务的情况下Propagation.REQUIRED修饰的内部方法会加入到外围方法的事务中,
    //所有Propagation.REQUIRED修饰的内部方法和外围方法均属于同一事务,只要一个方法回滚,整个事务均回滚。

    /*-----------------------------2.  测试   propagation = Propagation.REQUIRES_NEW--------------------------------------------------------------------------------*/

    /**
     * 2.1 外围方法没有开启事务。
     */

    //2.1.1 测试结果 都保存成功。 外围方法没有事务,插入学生、教师方法都在自己的事务中独立运行,
    //外围方法抛出异常回滚不会影响内部方法。
    @Override
    public void noTransactionExceptionRequiresNewRequiresNew() {
        Student student = new Student();
        student.setName("学生2.1.1");
        studentService.addRequiresNew(student);

        Teacher teacher = new Teacher();
        teacher.setName("教师2.1.1");
        teacherService.addRequiresNew(teacher);
        int i = 1 / 0;
    }

    //2.1.2 仅有学生保存成功。外围方法没有开启事务,插入学生方法和插入教师方法分别开启自己的事务,
    //插入教师方法抛出异常回滚,其他事务不受影响。
    @Override
    public void noTransactionRequiresNewRequiresNewException() {
        Student student = new Student();
        student.setName("学生2.1.2");
        studentService.addRequiresNew(student);

        Teacher teacher = new Teacher();
        teacher.setName("教师2.1.2");
        teacherService.addRequiresNewException(teacher);
    }
    //结论:通过这两个方法我们证明了在外围方法未开启事务的情况下Propagation.REQUIRES_NEW修饰的内部方法会新开启自己的事务,
    //且开启的事务相互独立,互不干扰。

    /**
     * 2.2 外围方法开启事务。
     */

    //2.2.1 学生插入失败,教师都插入成功。外围方法开启事务,插入 学生2.2.1 方法和外围方法一个事务,插入 教师2.2.1-1方法、
    //插入教师2.2.1-2方法分别在独立的新建事务中,外围方法抛出异常只回滚和外围方法同一事务的方法,
    //故插入学生2.2.1的方法回滚。
    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void transactionExceptionRequiredRequiresNewRequiresNew() {
        Student student = new Student();
        student.setName("学生2.2.1");
        studentService.addRequired(student);

        Teacher teacher1 = new Teacher();
        teacher1.setName("教师2.2.1-1");
        teacherService.addRequiresNew(teacher1);

        Teacher teacher2 = new Teacher();
        teacher2.setName("教师2.2.1-2");
        teacherService.addRequiresNew(teacher2);
        int i = 1 / 0;
    }

    //2.2.2 仅有 教师2.2.2-1 插入成功。外围方法开启事务,插入“学生2.2.2“方法和外围方法一个事务,插入“教师2.2.2-1”方法、
    //插入“教师2.2.2-2”方法分别在独立的新建事务中。插入“教师2.2.2-2”方法抛出异常,首先插入 “教师2.2.2-2”方法的事务被回滚,
    //异常继续抛出被外围方法感知,外围方法事务亦被回滚,故插入“学生2.2.2”方法也被回滚。
    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void transactionRequiredRequiresNewRequiresNewException() {
        Student student = new Student();
        student.setName("学生2.2.2");
        studentService.addRequired(student);

        Teacher teacher1 = new Teacher();
        teacher1.setName("教师2.2.2-1");
        teacherService.addRequiresNew(teacher1);

        Teacher teacher2 = new Teacher();
        teacher2.setName("教师2.2.2-2");
        teacherService.addRequiresNewException(teacher2);
    }

    //2.2.3 学生2.2.3 和 教师2.2.3-1 插入成功。
    //外围方法开启事务,插入“学生2.2.3”方法和外围方法一个事务,插入“教师2.2.3-1”方法、
    //插入“教师2.2.3-2”方法分别在独立的新建事务中。插入“教师2.2.3-2”方法抛出异常,首先插入“教师2.2.3-2”方法的事务被回滚,
    //异常被catch不会被外围方法感知,外围方法事务不回滚,故插入“学生2.2.3”方法插入成功。
    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void transactionRequiredRequiresNewRequiresNewExceptionTry() {
        Student student = new Student();
        student.setName("学生2.2.3");
        studentService.addRequired(student);

        Teacher teacher1 = new Teacher();
        teacher1.setName("教师2.2.3-1");
        teacherService.addRequiresNew(teacher1);

        Teacher teacher2 = new Teacher();
        teacher2.setName("教师2.2.3-2");
        try {
            teacherService.addRequiresNewException(teacher2);
        } catch (Exception e) {
            System.out.println("异常");
        }
    }
    //结论:在外围方法开启事务的情况下Propagation.REQUIRES_NEW修饰的内部方法依然会单独开启独立事务,
    //且与外部方法事务也独立,内部方法之间、内部方法和外部方法事务均相互独立,互不干扰。

    /*-----------------------------3.  测试   propagation = Propagation.NESTED--------------------------------------------------------------------------------*/

    /**
     * 3.1 外围方法没有开启事务。
     */

    //3.1.1 两个都插入成功。 外围方法未开启事务,插入“学生3.1.1”、“教师3.1.1”方法在自己的事务中独立运行,
    //外围方法异常不影响内部插入“学生3.1.1”、“教师3.1.1”方法独立的事务。
    @Override
    public void noTransactionExceptionNestedNested() {
        Student student = new Student();
        student.setName("学生3.1.1");
        studentService.addNested(student);

        Teacher teacher1 = new Teacher();
        teacher1.setName("教师3.1.1");
        teacherService.addNested(teacher1);
        int i = 1 / 0;
    }

    //3.1.2  仅学生3.1.2插入成功。 外围方法没有事务,插入“学生3.1.2”、“教师3.1.2”方法都在自己的事务中独立运行,
    //所以插入“教师3.1.2”方法抛出异常只会回滚插入“教师3.1.2”方法,插入“学生3.1.2”方法不受影响。
    @Override
    public void noTransactionNestedNestedException() {
        Student student = new Student();
        student.setName("学生3.1.2");
        studentService.addNested(student);

        Teacher teacher1 = new Teacher();
        teacher1.setName("教师3.1.2");
        teacherService.addNestedException(teacher1);
    }
    //结论:通过这两个方法我们证明了在外围方法未开启事务的情况下Propagation.NESTED和Propagation.REQUIRED作用相同,
    //修饰的内部方法都会新开启自己的事务,且开启的事务相互独立,互不干扰。

    /**
     * 3.2 外围方法开启事务
     */
    //3.2.1 都插入失败。 外围方法开启事务,内部事务为外围事务的子事务,外围方法回滚,内部方法也要回滚。
    @Override
    @Transactional
    public void transactionExceptionNestedNested() {
        Student student = new Student();
        student.setName("学生3.2.1");
        studentService.addNested(student);

        Teacher teacher1 = new Teacher();
        teacher1.setName("教师3.2.1");
        teacherService.addNested(teacher1);
        int i = 1 / 0;
    }

    //3.2.2 都插入失败。外围方法开启事务,内部事务为外围事务的子事务,内部方法抛出异常回滚,
    //且外围方法感知异常致使整体事务回滚。
    @Override
    @Transactional
    public void transactionNestedNestedException() {
        Student student = new Student();
        student.setName("学生3.2.2");
        studentService.addNested(student);

        Teacher teacher1 = new Teacher();
        teacher1.setName("教师3.2.2");
        teacherService.addNestedException(teacher1);
    }

    //3.2.3 仅学生3.2.3 插入成功。 外围方法开启事务,内部事务为外围事务的子事务,插入“教师3.2.3”内部方法抛出异常,
    //可以单独对子事务回滚。
    @Override
    @Transactional
    public void transactionNestedNestedExceptionTry() {
        Student student = new Student();
        student.setName("学生3.2.3");
        studentService.addNested(student);

        Teacher teacher1 = new Teacher();
        teacher1.setName("教师3.2.3");
        try {
            teacherService.addNestedException(teacher1);
        } catch (Exception e) {
            System.out.println("exception");
        }
        //结论:以上试验结果我们证明在外围方法开启事务的情况下Propagation.NESTED修饰的内部方法属于外部事务的子事务,
    //外围主事务回滚,子事务一定回滚,而内部子事务可以单独回滚而不影响外围主事务和其他子事务
    }



}

3.REQUIRED,REQUIRES_NEW,NESTED异同

由“1.2 ”和“3.2 ”对比,我们可知:
NESTED和REQUIRED修饰的内部方法都属于外围方法事务,如果外围方法抛出异常,这两种方法的事务都会被回滚。但是REQUIRED是加入外围方法事务,所以和外围事务同属于一个事务,一旦REQUIRED事务抛出异常被回滚,外围方法事务也将被回滚。而NESTED是外围方法的子事务,有单独的保存点,所以NESTED方法抛出异常被回滚,不会影响到外围方法的事务。

由上面测试 “2.2 ”和“3.2 ”对比,我们可知:
NESTED和REQUIRES_NEW都可以做到内部方法事务回滚而不影响外围方法事务。但是因为NESTED是嵌套事务,所以外围方法回滚之后,作为外围方法事务的子事务也会被回滚。而REQUIRES_NEW是通过开启新的事务实现的,内部事务和外围事务是两个事务,外围事务回滚不会影响内部事务。

完整源码地址:https://github.com/crossyourheart/TestTransactionPropagation.git
参考:https://blog.csdn.net/yuan520588/article/details/88919659

posted @ 2021-07-15 17:00  _lemonV  阅读(111)  评论(0编辑  收藏  举报