完整源码地址: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