spring事务有七中传播级别,分别是:
1、PROPERGATION_MANDATORY: 方法必须运行在一个事务中,不存在事务则抛出异常
2、PROPERGATION_NESTED: 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则按REQUIRED属性执行。
3、PROPERGATION_NEVER: 以非事务方式执行,如果当前存在事务,则抛出异常。
4、PROPERGATION_NOT_SUPPORT: 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
5、PROPERGATION_REQUIRED: spring默认的传播级别,如果当前没有事务,就创建一个新事务,如果当前存在事务,就加入该事务,该设置是最常用的设置。
6、PROPERGATION_REQUIRES_NEW: 新建一个自己的事务,不论当前是否存在事务
7、PROPERGATION_SUPPORT: 支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行。
接下来我们就验证下各个事务传播级别的影响
首先测试下PROPERGATION_REQUIRES_NEW事务的影响,代码如下:
首先写一个接口,其中主方法事务级别为默认PROPERGATION_REQUIRED,然后在写两个方法,传播级别分别设置为PROPERGATION_REQUIRED,和PROPERGATION_REQUIRES_NEW,外部抛出异常,看看事务会不会回滚
public interface TransactionalService {
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
void transaction();
}
@Service
public class TransactionalServiceImpl implements TransactionalService {
Logger logger = LoggerFactory.getLogger(TransactionalServiceImpl.class);
@Autowired
private EntityOneService entityOneService;
@Override
public void transaction() {
logger.info("====================== 开始运行transaction方法 ======");
entityOneService.add1();
entityOneService.add2();
throw new RuntimeException("RuntimeException");
}
}
public interface EntityOneService {
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
void add1();
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
void add2();
}
@Service
public class EntityOneServiceImpl implements EntityOneService {
@Autowired
private EntityOneDao entityOneDao;
@Override
public void add1() {
EntityOne entityOne = new EntityOne();
entityOne.setId(UUID.randomUUID().toString().replaceAll("-",""));
entityOne.setAttr1("attr1");
entityOne.setAttr2("attr2");
entityOneDao.save(entityOne);
}
@Override
public void add2() {
EntityOne entityOne = new EntityOne();
entityOne.setId(UUID.randomUUID().toString().replaceAll("-",""));
entityOne.setAttr1("attr7");
entityOne.setAttr2("attr8");
entityOneDao.save(entityOne);
}
}
执行代码后发现,add2方法中向数据库添加数据成功,add1添加失败,由此看来外部事务回滚不会造成PROPERGATION_REQUIRES_NEW回滚,PROPERGATION_REQUIRES_NEW事务的数据一样可以存储。
现在修改代码把add2中抛出异常,外层不抛出异常:
@Service
public class TransactionalServiceImpl implements TransactionalService {
Logger logger = LoggerFactory.getLogger(TransactionalServiceImpl.class);
@Autowired
private EntityOneService entityOneService;
@Override
public void transaction() {
logger.info("====================== 开始运行transaction方法 ======");
entityOneService.add1();
entityOneService.add2();
}
}
@Service
public class EntityOneServiceImpl implements EntityOneService {
@Autowired
private EntityOneDao entityOneDao;
@Override
public void add1() {
EntityOne entityOne = new EntityOne();
entityOne.setId(UUID.randomUUID().toString().replaceAll("-",""));
entityOne.setAttr1("attr1");
entityOne.setAttr2("attr2");
entityOneDao.save(entityOne);
}
@Override
public void add2() {
EntityOne entityOne = new EntityOne();
entityOne.setId(UUID.randomUUID().toString().replaceAll("-",""));
entityOne.setAttr1("attr7");
entityOne.setAttr2("attr8");
entityOneDao.save(entityOne);
throw new RuntimeException("抛出异常");
}
}
运行结果发现,内部事务PROPERGATION_REQUIRES_NEW抛出异常,外部事务也同样回滚,add1和add2方法都执行失败。
如果修改代码在主方法中捕获异常则可以add1执行成功。
@Service
public class TransactionalServiceImpl implements TransactionalService {
Logger logger = LoggerFactory.getLogger(TransactionalServiceImpl.class);
@Autowired
private EntityOneService entityOneService;
@Override
public void transaction() {
logger.info("====================== 开始运行transaction方法 ======");
entityOneService.add1();
try{
entityOneService.add2();
} catch (Exception e) {
e.printStackTrace();
}
}
}
但是如果把add2中的隔离级别改成PROPERGATION_REQUIRED,则两个方法都是执行失败,因为是在同一个事务中,即使被捕获了异常,同样是事务回滚,所以同一个事务的其他方法,也同样回滚。
由此得出结论,外层PROPERGATION_REQUIRED事务,其调用的方法如果也用的是此传播机制,则加如此事务,如果有一个异常,所有都回滚。如果内部方法隔离级别是PROPERGATION_REQUIRES_NEW,外部事务回滚不会影响此事务,但是此事务异常如果没有被捕捉,会影响外部事务。
由于JPA不支持嵌套事务PROPERGATION_NESTED,可以使用jdbcTemplate测试,暂时就没做测试。
// 2020年7月29补充:
在新做了一个测试,在同一个类中,add1方法传播机制是 REQUIRED,add2方法的传播机制是 REQUIRES_NEW,
@Override
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
public void add1() {
EntityOne entityOne = new EntityOne();
entityOne.setId(UUID.randomUUID().toString().replaceAll("-",""));
entityOne.setAttr1("attr1");
entityOne.setAttr2("attr2");
entityOneDao.save(entityOne);
add2();
throw new RuntimeException("ccccc");
}
@Override
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
public void add2() {
EntityOne entityOne = new EntityOne();
entityOne.setId(UUID.randomUUID().toString().replaceAll("-",""));
entityOne.setAttr1("attr7");
entityOne.setAttr2("attr8");
entityOneDao.save(entityOne);
}
但是在调用add1方法后,按照分析,add2是新的事务,和add1的事务不同,所以add1报错,add1应该会回滚,add2会成功。但是结果却显示add1和add2都失败了。
最后查资料才明白Spring采用动态代理(AOP)实现对bean的管理和切片,它为我们的每个class生成一个代理对象。只有在代理对象之间进行调用时,可以触发切面逻辑。所以内部方法调用不会被拦截,所以add2上就相当于加入到了add1的这个事务。所以出错之后两个都失败了,因为是同一个事务里。
总结:对于spring的事务,同类的调用一般只要写在最外层的方法就行了,这样内部方法就同样加入了这个事务。
对于不同事务之间的调用,最好分布在多个类之间,这样才能有效果。