Spring事务传播特性

246 阅读8分钟

事务传播行为定义:

事务传播行为: 当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。

一、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个事务传播特性的说明和案例演示。今后对事务传播的更深入理解,也会在此文继续更新分享。