Spring事务传播行为

2018-12-01 17:01:26

事务传播行为,就是多个事务方法相互调用时,事务如何在这些方法间传播,Spring支持7种事务传播行为

事务传播行为 说明
PROPAGETION_REQUIRED 如果当前没有事务,就新建一个事务;如果已经存在一个事务,就加入到这个事务中,这个是最常用的,也是Spring默认的事务传播行为
PROPAGATION_SUPPORTS 支持当前事务,如果当前没有事务,就以非事务方式执行
PROPAGATION_MANDATORY 使用当前事务,如果当前没有事务就抛出异常
PROPAGATION_REQUIRES_NEW 新建事务,如果当前存在事务,就把当前事务挂起
PROGAGATION_NOT_SUPPORTED 以非事务的方式运行,如果当前存在事务,就把当前事务挂起
PROGAGATION_NEVER 以非事务方式运行,如果当前存在事务,就抛出异常
PROGAGATION_NESTED 如果当前存在事务,就在这个嵌套事务内运行;如果当前没有事务,就新建一个事务

Jpa事务管理器不支持最后一个

  • 如果直接调用同一个Bean中的方法是不存在事务传播行为的,因为spring事务是基于动态代理,而this并非指向代理对象,所以想要让同一个类中的事务传播生效,需要获取代理对象,可以用AopContext.currentProxy()来获取,也可以在Bean中注入ApplicationContext,然后从里面再获取这个Bean。
  • spring事务是基于动态代理的,所以会有些方法无法使用spring事务,比如private修饰的方法,这些方法是无法开启事务的,但有的书上说这些方法虽然无法开启事务,但是可以被传播事务。的确,这些无法开启事务的方法被一个有事务的方法调用时会和调用它的方法处于一个事务中,但是,这和事务传播并没有什么关系,这种情况和上面的调用同一个Bean中的方法是一样的,仅仅是因为被调用而处于一个事务中。

新建一个spring boot项目,添加MySQL和spring jdbc的依赖,然后配置好数据库,然后配置spring jdbc事务管理器的包日志级别为debug

  1. logging.level.org.springframework.jdbc.datasource=debug

MsgService接口:

  1. public interface MsgService {
  2. void A();
  3. void B();
  4. }

在方法A()里调用B(),观察不同的事务传播行为

  1. @Resource
  2. private MsgService msgService;
  3. @Test
  4. public void contextLoads() {
  5. msgService.A();
  6. }

然后分别配置B()的事务传播为七个中的之一:

  1. PROPAGETION_REQUIRED:如果当前没有事务,就新建一个事务;如果已经存在一个事务,就加入到这个事务中
  1. @Service
  2. public class MsgServiceImpl implements MsgService {
  3. private static final Logger LOGGER = LoggerFactory.getLogger(MsgServiceImpl.class);
  4. @Resource
  5. private ApplicationContext applicationContext;
  6. @Override
  7. @Transactional(propagation = Propagation.REQUIRED)
  8. public void A() {
  9. LOGGER.debug("start A");
  10. MsgService msgService = (MsgService) applicationContext.getBean("msgServiceImpl");
  11. msgService.B();
  12. LOGGER.debug("end A");
  13. }
  14. @Override
  15. @Transactional(propagation = Propagation.REQUIRED)
  16. public void B() {
  17. LOGGER.debug("start B");
  18. LOGGER.debug("end B");
  19. }
  20. }

运行结果:

可以看到,方法B()加入到了A()的事务中,所以前后一共只用了一个数据库连接
然后,让B()抛出一个异常,如果A()没有捕获,那事务肯定要回滚,如果捕获了呢?

  1. @Override
  2. @Transactional(propagation = Propagation.REQUIRED)
  3. public void A() {
  4. LOGGER.debug("start A");
  5. MsgService msgService = (MsgService) applicationContext.getBean("msgServiceImpl");
  6. try {
  7. msgService.B();
  8. } catch (Exception e) {
  9. }
  10. System.out.println(TransactionAspectSupport.currentTransactionStatus().isRollbackOnly());
  11. LOGGER.debug("end A");
  12. }
  13. @Override
  14. @Transactional(propagation = Propagation.REQUIRED)
  15. public void B() {
  16. LOGGER.debug("start B");
  17. throw new RuntimeException();
  18. }

依然还会回滚
A()中加了一句获取当前事务状态的代码,可以看到B()抛出异常后,事务被设置为rollbackOnlyA()结束后事务管理器会根据事务的状态提交事务,如果事务已经被标识为rollbackOnly,则执行回滚。

  1. PROPAGATION_SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行
    A()有事务时:
  1. @Override
  2. @Transactional(propagation = Propagation.REQUIRED)
  3. public void A() {
  4. LOGGER.debug("start A");
  5. MsgService msgService = (MsgService) applicationContext.getBean("msgServiceImpl");
  6. msgService.B();
  7. LOGGER.debug("end A");
  8. }
  9. @Override
  10. @Transactional(propagation = Propagation.SUPPORTS)
  11. public void B() {
  12. LOGGER.debug("start B");
  13. LOGGER.debug("end B");
  14. }

B()加入到A()的事务中:


A()没有事务时,注释掉A()方法上的事务注解:

可以看到B()以非事务方式运行,如果此时在B()里面抛出一个异常,由于没有事务,所以无法回滚:

  1. PROPAGATION_MANDATORY:使用当前事务,如果当前没有事务就抛出异常
  1. @Override
  2. @Transactional(propagation = Propagation.REQUIRED)
  3. public void A() {
  4. LOGGER.debug("start A");
  5. MsgService msgService = (MsgService) applicationContext.getBean("msgServiceImpl");
  6. msgService.B();
  7. LOGGER.debug("end A");
  8. }
  9. @Override
  10. @Transactional(propagation = Propagation.MANDATORY)
  11. public void B() {
  12. LOGGER.debug("start B");
  13. LOGGER.debug("end B");
  14. }

B()加入到了A()的事务中:

此时注释掉A()上的事务注解,就会抛异常:

  1. PROPAGATION_REQUIRES_NEW:新建事务,如果当前存在事务,就把当前事务挂起
    A()有事务:
  1. @Override
  2. @Transactional(propagation = Propagation.REQUIRED)
  3. public void A() {
  4. LOGGER.debug("start A");
  5. MsgService msgService = (MsgService) applicationContext.getBean("msgServiceImpl");
  6. msgService.B();
  7. LOGGER.debug("end A");
  8. }
  9. @Override
  10. @Transactional(propagation = Propagation.REQUIRES_NEW)
  11. public void B() {
  12. LOGGER.debug("start B");
  13. LOGGER.debug("end B");
  14. }

运行结果:

当前事务挂起后,内层方法B()新建了一个事务,由于事务最终要依赖于数据库连接,所以内层事务又获取了一个连接,在B()方法执行完后,就提交事务。此时,如果在B()中抛出一个异常,如果A()没有捕获,则异常会经过A()抛出,所以内层和外层事务均会回滚,如果A()捕获了,则只回滚内层事务而不影响外层事务。
B()新建的内层事务与当前事务是完全互不干扰的,无论是提交还是回滚事务。
A()没有事务时,则B()会新建一个事务:

  1. PROGAGATION_NOT_SUPPORTED:以非事务的方式运行,如果当前存在事务,就把当前事务挂起
  2. PROGAGATION_NEVER:以非事务方式运行,如果当前存在事务,就抛出异常
  3. PROGAGATION_NESTED:如果当前存在事务,就在这个嵌套事务内运行;如果当前没有事务,就新建一个事务
  1. @Override
  2. @Transactional(propagation = Propagation.REQUIRED)
  3. public void A() {
  4. LOGGER.debug("start A");
  5. MsgService msgService = (MsgService) applicationContext.getBean("msgServiceImpl");
  6. msgService.B();
  7. LOGGER.debug("end A");
  8. }
  9. @Override
  10. @Transactional(propagation = Propagation.NESTED)
  11. public void B() {
  12. LOGGER.debug("start B");
  13. LOGGER.debug("end B");
  14. }

可以看到,B()并没有新建一个事务,而是在B()开始时创建了一个savepoint,在B()成功执行后释放该savepoint,所以,如果在B()中抛出一个异常,被A()捕获,事务会回滚到这个savepoint然后继续执行A()中其他代码。


0
1
0

添加评论

正在回复:
取消
2
0
1
0