事务传播行为定义:
事务传播行为: 当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。
一、Spring定义了七种传播行为:
事务,并在自己的事务中运行。Spring定义了七种传播行为:
传播行为和含义:
PROPAGATION_REQUIRED:表示当前方法必须运行在事务中。如果当前事务存在,方法将会在该事务中运行。否则,会启动一个新的事务。
PROPAGATION_SUPPORTS:表示当前方法不需要事务上下文,但是如果存在当前事务的话,那么该方法会在这个事务中运行。
PROPAGATION_MANDATORY:表示该方法必须在事务中运行,如果当前事务不存在,则会抛出一个异常。
PROPAGATION_REQUIRED_NEW: 表示当前方法必须运行在它自己的事务中。一个新的事务将被启动。如果存在当前事务,在该方法执行期间,当前事务会被挂起。如果使用JTATransactionManager的话,则需要访问TransactionManager。
PROPAGATION_NOT_SUPPORTED: 表示该方法不应该运行在事务中。如果存在当前事务,在该方法运行期间,当前事务将被挂起。如果使用JTATransactionManager的话,则需要访问TransactionManager。
PROPAGATION_NEVER:表示当前方法不应该运行在事务上下文中。如果当前正有一个事务在运行,则会抛出异常。
PROPAGATION_NESTED:表示如果当前已经存在一个事务,那么该方法将会在嵌套事务中运行。嵌套的事务可以独立于当前事务进行单独地提交或回滚。如果当前事务不存在,那么其行为与PROPAGATION_REQUIRED一样。注意各厂商对这种传播行为的支持是有所差异的。可以参考资源管理器的文档来确认它们是否支持嵌套事务。
PROPAGATION_NESTED:表示如果当前已经存在一个事务,那么该方法将会在嵌套事务中运行。嵌套的事务可以独立于当前事务进行单独地提交或回滚。如果当前事务不存在,那么其行为与PROPAGATION_REQUIRED一样。注意各厂商对这种传播行为的支持是有所差异的。可以参考资源管理器的文档来确认它们是否支持嵌套事务。
二、示例:
数据库模型:
create table person (
id int(18) primary key,
name varchar(10) not null,
age int(3) not null default 0,
gender int(1) not null,
p_desc varchar(64)
);
初始数据:
1 张三 15 0 他叫张三
2 李四 19 0 他叫李四
3 韩梅梅 28 1 他叫韩梅梅,女的
测试代码:
@Service
public class PersonUpdateService {
@Resource
private PersonInsertService personInsertService;
@Resource
private PersonMapper personMapper;
public void updatePerson2() {
Person p2 = new Person();
p2.setId(2);
p2.setpDesc("更新李四的说明");
personMapper.updateByPrimaryKeySelective(p2);
personInsertService.insertPerson4();
}
}
@Service
public class PersonInsertService {
@Resource
private PersonMapper personMapper;
public void insertPerson4() {
Person p4 = new Person();
p4.setId(4);
p4.setName("王五");
p4.setAge(42);
p4.setGender(0);
p4.setpDesc("新增的王五");
personMapper.insert(p4);
throw new RuntimeException("测试异常");
}
}
@RunWith(SpringRunner.class)
@SpringBootTest(classes = DemoApplication.class)
public class PersonServiceTest {
@Resource
private PersonUpdateService personUpdateService;
@Test
public void testPropagationRequired() {
personUpdateService.updatePerson2();
}
}
未加事务注解@Transational时的运行结果:
1 张三 15 0 他叫张三
2 李四 19 0 更新李四的说明
3 韩梅梅 28 1 他叫韩梅梅,女的
4 王五 42 0 新增的王五
(抛异常前的数据库操作,都commit了。)
1.PROPAGATION_REQUIRED
测试代码:
@Service
public class PersonUpdateService {
@Resource
private PersonInsertService personInsertService;
@Resource
private PersonMapper personMapper;
@Transactional(propagation = Propagation.REQUIRED)
public void updatePerson2() {
Person p2 = new Person();
p2.setId(2);
p2.setpDesc("更新李四的说明");
personMapper.updateByPrimaryKeySelective(p2);
personInsertService.insertPerson4();
}
}
@Service
public class PersonInsertService {
@Resource
private PersonMapper personMapper;
@Transactional(propagation = Propagation.REQUIRED)
public void insertPerson4() {
Person p4 = new Person();
p4.setId(4);
p4.setName("王五");
p4.setAge(42);
p4.setGender(0);
p4.setpDesc("新增的王五");
personMapper.insert(p4);
throw new RuntimeException("测试异常");
}
}
加事务注解@Transational(propagation = Propagation.REQUIRED)时的运行结果:
1 张三 15 0 他叫张三
2 李四 19 0 他叫李四
3 韩梅梅 28 1 他叫韩梅梅,女的
(可以看出,内层PersonInsertService.insertPerson4方法抛出异常后,外层personUpdateService.updateByPrimaryKeySelective的更新也一并回滚)。
2.PROPAGATION_SUPPORTS
a.外层方法有事务:
测试代码:
@Service
public class PersonUpdateService {
@Resource
private PersonInsertService personInsertService;
@Resource
private PersonMapper personMapper;
@Transactional(propagation = Propagation.REQUIRED) //外层方法有事务
public void updatePerson2() {
... //方法逻辑同上个例子
}
}
@Service
public class PersonInsertService {
@Resource
private PersonMapper personMapper;
@Transactional(propagation = Propagation.SUPPORTS)
public void insertPerson4() {
... //方法逻辑同上个例子
}
}
内层insertPerson4方法加事务注解@Transational(propagation = Propagation.SUPPORTS)时的运行结果:
1 张三 15 0 他叫张三
2 李四 19 0 他叫李四
3 韩梅梅 28 1 他叫韩梅梅,女的
(可以看出,外层personUpdateService.updateByPrimaryKeySelective的@Transational事务起作用)。
b.外层方法不使用事务:
测试代码:
@Service
public class PersonUpdateService {
@Resource
private PersonInsertService personInsertService;
@Resource
private PersonMapper personMapper;
//外层方法不使用事务
public void updatePerson2() {
... //方法逻辑同上个例子
}
}
@Service
public class PersonInsertService {
@Resource
private PersonMapper personMapper;
@Transactional(propagation = Propagation.SUPPORTS)
public void insertPerson4() {
... //方法逻辑同上个例子
}
}
内层insertPerson4方法加事务注解@Transational(propagation = Propagation.SUPPORTS)时的运行结果:
1 张三 15 0 他叫张三
2 李四 19 0 更新李四的说明
3 韩梅梅 28 1 他叫韩梅梅,女的
4 王五 42 0 新增的王五
(可以看出,外层personUpdateService.updateByPrimaryKeySelective的@Transational注解去除后,内层@Transactional(propagation = Propagation.SUPPORTS)也没有启动事务)。
3.PROPAGATION_MANDATORY
测试代码:
@Service
public class PersonUpdateService {
@Resource
private PersonInsertService personInsertService;
@Resource
private PersonMapper personMapper;
//外层方法无事务
public void updatePerson2() {
... //方法逻辑同上个例子
}
}
@Service
public class PersonInsertService {
@Resource
private PersonMapper personMapper;
@Transactional(propagation = Propagation.MANDATORY)
public void insertPerson4() {
... //方法逻辑同上个例子
}
}
内层insertPerson4方法加事务注解@Transational(propagation = Propagation.MANDATORY)时的运行结果:代码抛出异常: org.springframework.transaction.IllegalTransactionStateException: No existing transaction found for transaction marked with propagation 'mandatory'
4.PROPAGATION_REQUIRED_NEW
由于这个事务的关键点在于外层事务的“挂起”,所以测试例子需要用另外一个比对:
a.沿用PROPAGATION_REQUIRED的例子:
测试代码:
@Transactional(propagation = Propagation.REQUIRED) //外层用REQUIRED
public void updatePerson2() {
Person p2 = new Person();
p2.setId(2);
p2.setpDesc("更新李四的说明");
personMapper.updateByPrimaryKeySelective(p2);
try {
personInsertService.insertPerson4();
} catch (RuntimeException re) {
re.printStackTrace();
}
}
@Transactional(propagation = Propagation.REQUIRED)
public void insertPerson4() {
Person p4 = new Person();
p4.setId(4);
p4.setName("王五");
p4.setAge(42);
p4.setGender(0);
p4.setpDesc("新增的王五");
personMapper.insert(p4);
// 这里添加了查询外层修改的id为2的Person对象,
// 用于验证外层事务在内层的可见性
PersonExample p2e = new PersonExample();
PersonExample.Criteria c = p2e.createCriteria();
c.andIdEqualTo(2);
System.out.println(personMapper.selectByExample(p2e).get(0));
throw new RuntimeException("测试异常");
}
运行结果:在抛出测试异常前,打印出的id为2的Person对象的信息为:Person [Hash = 1266759621, id=2, name=李四, age=19, gender=0, pDesc=更新李四的说明]。pDesc这一项已经在外层从“他叫李四”修改为“更新李四的说明”,说明外层事务跟内层是同一个。
b.用PROPAGATION_REQUIRED_NEW的例子:
测试代码:
@Transactional(propagation = Propagation.REQUIRED)
public void updatePerson2() {
Person p2 = new Person();
p2.setId(2);
p2.setpDesc("更新李四的说明");
personMapper.updateByPrimaryKeySelective(p2);
try {
personInsertService.insertPerson4();
} catch (RuntimeException re) {
re.printStackTrace();
}
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void insertPerson4() {
Person p4 = new Person();
p4.setId(4);
p4.setName("王五");
p4.setAge(42);
p4.setGender(0);
p4.setpDesc("新增的王五");
personMapper.insert(p4);
PersonExample p2e = new PersonExample();
PersonExample.Criteria c = p2e.createCriteria();
c.andIdEqualTo(2);
// 注意这里打印的信息与上例的差异
System.out.println(personMapper.selectByExample(p2e).get(0));
throw new RuntimeException("测试异常");
}
运行结果:在抛出测试异常前,打印出的id为2的Person对象的信息为:Person [Hash = 111238003, id=2, name=李四, age=19, gender=0, pDesc=他叫李四]。然后照样抛出了测试异常:java.lang.RuntimeException: 测试异常
at geektime.spring.data.mybatis.service.PersonInsertService.insertPerson4(PersonInsertService.java:33)
而执行完测试后的Person表数据状态为:
1 张三 15 0 他叫张三
2 李四 19 0 更新李四的说明
3 韩梅梅 28 1 他叫韩梅梅,女的
现在跟上例不一样,内层insertPerson4方法中事务传播特性用了REQUIRES_NEW。也证明了在这个例子中内层事务已经与外层不一样,所以外层事务对id=2的李四的pDesc修改,内层事务也不可见;而在内层事务抛异常时,由于外层事务是独立的,而且事务是“挂起”状态,所以内层事务回滚后(没有插入“王五”的数据),并不影响外层事务对“李四”数据的修改,外层事务继续进行并提交了,最终结果就变成“李四”的pDesc被更新为“更新李四的说明”。
5.PROPAGATION_NOT_SUPPORTED
测试代码:
@Transactional(propagation = Propagation.REQUIRED)
public void updatePerson2() {
Person p2 = new Person();
p2.setId(2);
p2.setpDesc("更新李四的说明");
personMapper.updateByPrimaryKeySelective(p2);
try {
personInsertService.insertPerson4();
} catch (RuntimeException re) {
re.printStackTrace();
}
}
@Transactional(propagation = Propagation.NOT_SUPPORTED) //换成NOT_SUPPORTED
public void insertPerson4() {
Person p4 = new Person();
p4.setId(4);
p4.setName("王五");
p4.setAge(42);
p4.setGender(0);
p4.setpDesc("新增的王五");
personMapper.insert(p4);
PersonExample p2e = new PersonExample();
PersonExample.Criteria c = p2e.createCriteria();
c.andIdEqualTo(2);
System.out.println(personMapper.selectByExample(p2e).get(0));
throw new RuntimeException("测试异常");
}
这个案例的运行结果,有点像上例REQUIRED_NEW的情况,内层打印的信息:Person [Hash = 1021082377, id=2, name=李四, age=19, gender=0, pDesc=他叫李四],同样因为内层与外层不是同在事务中,所以没有见到外层对pDesc的修改;但这次不同的是,因为内层NOT_SUPPORTED特性,实际上内层insertPerson4方法是没有事务的,导致插入“王五”后抛出的异常并不能使插入操作回滚。所以最终的数据状况如下:
1 张三 15 0 他叫张三
2 李四 19 0 更新李四的说明
3 韩梅梅 28 1 他叫韩梅梅,女的
4 王五 42 0 新增的王五
可以见到,外层对李四的修改操作的事务提交了,内层插入“王五”也成功了。
6.PROPAGATION_NEVER
测试代码:
@Service
public class PersonUpdateService {
@Resource
private PersonInsertService personInsertService;
@Resource
private PersonMapper personMapper;
@Transactional //外层方法使用事务
public void updatePerson2() {
... //方法逻辑同上个例子
}
}
@Service
public class PersonInsertService {
@Resource
private PersonMapper personMapper;
@Transactional(propagation = Propagation.NEVER)
public void insertPerson4() {
... //方法逻辑同上个例子
}
}
内层insertPerson4方法加事务注解@Transational(propagation = Propagation.NEVER)时的运行结果:代码抛出如下异常: org.springframework.transaction.IllegalTransactionStateException: Existing transaction found for transaction marked with propagation 'never'
7.PROPAGATION_NESTED
测试代码:
@Service
public class PersonUpdateService {
@Resource
private PersonInsertService personInsertService;
@Resource
private PersonMapper personMapper;
@Transactional
public void updatePerson2() {
Person p2 = new Person();
p2.setId(2);
p2.setpDesc("更新李四的说明");
personMapper.updateByPrimaryKeySelective(p2);
try {
personInsertService.insertPerson4();
} catch (RuntimeException re) {
re.printStackTrace();
}
}
@Service
public class PersonInsertService {
@Resource
private PersonMapper personMapper;
@Transactional(propagation = Propagation.NESTED)
public void insertPerson4() {
Person p4 = new Person();
p4.setId(4);
p4.setName("王五");
p4.setAge(42);
p4.setGender(0);
p4.setpDesc("新增的王五");
personMapper.insert(p4);
throw new RuntimeException("测试异常");
}
}
内层insertPerson4方法加事务注解@Transational(propagation = Propagation.NESTED)时的运行结果:
1 张三 15 0 他叫张三
2 李四 19 0 更新李四的说明
3 韩梅梅 28 1 他叫韩梅梅,女的
(可以看出,虽然内层事务因为异常而回滚,但不影响外层personUpdateService.updateByPrimaryKeySelective的事务提交)。
以上就是对7个事务传播特性的说明和案例演示。今后对事务传播的更深入理解,也会在此文继续更新分享。