为什么写这个博客?
看了Spring事务传播行为详解这篇博文之后自己将文中的代码实现了一下,加深印象,同时按照自己的习惯整理一篇博客留存
某一个事务传播行为修饰的方法被嵌套进另一个方法的事务时如何传播。
public void methodA(){
methodB();
//doSomething
}
@Transaction(Propagation=XXX)
public void methodB(){
//doSomething
}
方法A嵌套调用方法B,方法B的事务传播行为由注解@Transaction(Propagation=XXX)来决定
这里的方法A是没有开启事务的
事务传播行为类型 | 说明 |
---|---|
Propagation.REQUIRED | 外围有事务,加入这个事务;外围无事务,使用自己的事务(最常用) |
Propagation.SUPPORTS | 外围有事务,加入这个事务;外围无事务,以非事务方式执行 |
Propagation.MANDATORY | 外围有事务,加入这个事务;外围无事务,就抛出异常 |
Propagation.REQUIRES_NEW | 外围有事务,把外围事务挂起;使用自己的事务 |
Propagation.NOT_SUPPORTED | 外围有事务,把外围事务挂起;以非事务方式执行操作 |
Propagation.NEVER | 以非事务方式执行,如果外围存在事务,则抛出异常 |
Propagation.NESTED | 外围有事务,在嵌套事务内执行;外围无事务,使用自己的事务 |
实体类
@Setter
@Getter
@EqualsAndHashCode
public class User1 {
private Integer id;
private String name;
}
@Setter
@Getter
@EqualsAndHashCode
public class User2 {
private Integer id;
private String name;
}
/**
* user1内部方法,无异常
* @param user1
*/
@Override
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
public void addUser1(User1 user1) {
user1Mapper.insert(user1);
}
/**
* user2内部方法1,无异常
* @param user2
*/
@Override
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
public void addUser2(User2 user2) {
user2Mapper.insert(user2);
}
/**
* user2内部方法2,有异常
* @param user2
*/
@Override
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
public void addUser2Exception(User2 user2) {
user2Mapper.insert(user2);
throw new RuntimeException();
}
public void testTransactionalREQUIRED(){
User1 user1 = new User1();
user1.setName("张三");
user1Service.addUser1(user1);
User2 user2 = new User2();
user2.setName("李四");
user2Service.addUser2(user2);
throw new RuntimeException();
}
public void testTransactionalREQUIRED2(){
User1 user1 = new User1();
user1.setName("张三");
user1Service.addUser1(user1);
User2 user2 = new User2();
user2.setName("李四");
user2Service.addUser2Exception(user2);
}
public void testTransactionalREQUIRED3(){
User1 user1 = new User1();
user1.setId(1);
user1.setName("张三2");
user1Service.addUser1(user1);
User2 user2 = new User2();
user2.setId(1);
user2.setName("李四2");
try {
user2Service.addUser2Exception(user2);
} catch (Exception e) {
System.out.println("捕获异常");
}
}
分别执行2个外部方法,结果如下:
序号 | 数据库结果 | 结果分析 |
---|---|---|
1 | “张三”插入,“李四”插入 | 外部方法未开启事务,插入“张三”、“李四”方法在自己的事务中独立运行;外部方法异常不影响内部插入“张三”“李四”方法独立的事务 |
2 | “张三”插入,“李四”未插入 | 外部方法未开启事务,插入“张三”、“李四”方法在自己的事务中独立运行;所以插入“李四”方法抛出异常只会回滚插入“李四”的方法,不影响插入“张三”的方法 |
结论:外部方法未开启事务的情况下,Propagation.REQUIRED修饰的内部方法使用自己的事务,且互不干扰
分别执行2个外部方法,结果如下:
序号 | 数据库结果 | 结果分析 |
---|---|---|
1 | “张三”未插入,“李四”未插入 | 外部方法开启事务,内部方法加入外部方法事务,外部方法回滚,内部方法也回滚 |
2 | “张三”未插入,“李四”未插入 | 外部方法开启事务,内部方法加入外部方法事务,内部方法抛出异常回滚,外部方法感知异常致使整体事务回滚 |
3 | “张三”未插入,“李四”未插入 | 外部方法开启事务,内部方法加入外部方法事务,内部方法抛出异常回滚,即便方法被catch不被外围方法感知,整个事务依然会滚 |
结论:外部方法开启事务的情况下,Propagation.REQUIRED修饰的内部方法使用外部方法的事务,任意位置出现异常,整体回滚()
外部方法无事务时,内部方法以非事务的方式运行
序号 | 数据库结果 | 结果分析 |
---|---|---|
1 | “张三”插入,“李四”插入 | 外部方法未开启事务,内部方法以非事务方式运行 |
2 | “张三”插入,“李四”插入 | 外部方法未开启事务,内部方法以非事务方式运行 |
外部方法有事务时,内部方法和外部方法是一个整体事务,任意位置出现异常,整体回滚
外部方法有事务时,内部方法和外部方法是一个整体事务,任意位置出现异常,整体回滚
序号 | 数据库结果 | 结果分析 |
---|---|---|
1 | “张三”插入,“李四”插入 | 外部方法未开启事务,插入“张三”、“李四”方法在自己的事务中独立运行;外部方法异常不影响内部插入“张三”“李四”方法独立的事务 |
2 | “张三”插入,“李四”未插入 | 外部方法未开启事务,插入“张三”、“李四”方法在自己的事务中独立运行;所以插入“李四”方法抛出异常只会回滚插入“李四”的方法,不影响插入“张三”的方法 |
序号 | 数据库结果 | 结果分析 |
---|---|---|
1 | “张三”插入,“李四”插入 | 外部方法开启事务,把外部事务挂起,外部方法异常不会使内部方法回滚 |
2 | “张三”插入,“李四”未插入 | 外部方法开启事务,把外部事务挂起,内部方法独立运行自己的事务,不会互相影响 |
全部插入成功;无论外部方法是否有事务,都以非事务方式运行
全部插入成功;外部无事务,以非事务方式运行,插入成功后发生异常不会回滚代码
序号 | 数据库结果 | 结果分析 |
---|---|---|
1 | “张三”插入,“李四”插入 | 外部方法未开启事务,插入“张三”、“李四”方法在自己的事务中独立运行;外部方法异常不影响内部插入“张三”“李四”方法独立的事务 |
2 | “张三”插入,“李四”未插入 | 外部方法未开启事务,插入“张三”、“李四”方法在自己的事务中独立运行;所以插入“李四”方法抛出异常只会回滚插入“李四”的方法,不影响插入“张三”的方法 |
序号 | 数据库结果 | 结果分析 |
---|---|---|
1 | “张三”未插入,“李四”未插入 | 外部方法开启事务,内部方法事务为外部方法事务的子事务,外部方法回滚,内部方法也回滚 |
2 | “张三”未插入,“李四”未插入 | 外部方法开启事务,内部方法事务为外部方法事务的子事务,内部方法抛出异常回滚,外部方法感知异常致使整体事务回滚 |
3 | “张三”插入,“李四”未插入 | 外部方法开启事务,内部方法事务为外部方法事务的子事务,内部方法被catch不被外围方法感知,仅对其进行单独回滚,不影响其它事务 |
NESTED 和 REQUIRED 修饰的内部方法都属于外部方法事务,
如果外部方法抛出异常,这两种方法的事务都会被回滚。
但是 REQUIRED 是加入外部方法事务,所以和外部事务同属于一个事务,一旦 REQUIRED 事务抛出异常被回滚,外部方法事务也将被回滚。
而 NESTED 是外部方法的子事务,有单独的保存点,所以 NESTED 方法抛出异常被回滚,不会影响到外部方法的事务。
NESTED 和 REQUIRES_NEW 都可以做到内部方法事务回滚而不影响外部方法事务。
但是因为 NESTED 是嵌套事务,所以外部方法回滚之后,作为外部方法事务的子事务也会被回滚。
而 REQUIRES_NEW 是通过开启新的事务实现的,内部事务和外部事务是两个事务,外部事务回滚不会影响内部事务。
假设我们有一个注册的方法,方法中调用添加积分的方法,如果我们希望添加积分不会影响注册流程(即添加积分执行失败回滚不能使注册方法也回滚),我们会这样写:
@Service
public class UserServiceImpl implements UserService {
@Transactional(rollbackFor = Exception.class)
public void register(User user){
try {
membershipPointService.addPoint(Point point);
} catch (Exception e) {
//省略...
}
//省略...
}
//省略...
}
我们还规定注册失败要影响addPoint()方法(注册方法回滚添加积分方法也需要回滚),那么addPoint()方法就需要这样实现:
@Service
public class MembershipPointServiceImpl implements MembershipPointService{
@Transactional(rollbackFor = Exception.class, propagation = Propagation.NESTED)
public void addPoint(Point point){
try {
recordService.addRecord(Record record);
} catch (Exception e) {
//省略...
}
//省略...
}
//省略...
}
我们注意到了在addPoint()中还调用了addRecord()方法,这个方法用来记录日志。他的实现如下:
@Service
public class RecordServiceImpl implements RecordService{
@Transactional(rollbackFor = Exception.class, propagation = Propagation.NOT_SUPPORTED)
public void addRecord(Record record){
//省略...
}
//省略...
}
我们注意到addRecord()方法中propagation = Propagation.NOT_SUPPORTED,因为对于日志无所谓精确,可以多一条也可以少一条,所以addRecord()方法本身和外围addPoint()方法抛出异常都不会使addRecord()方法回滚,并且addRecord()方法抛出异常也不会影响外围addPoint()方法的执行。