[Spring]事务的七个传播行为

本文详细解析了Spring中的事务传播行为,包括PROPAGATION_REQUIRED、PROPAGATION_SUPPORTS、PROPAGATION_REQUIRES_NEW、PROPAGATION_NOT_SUPPORTED、PROPAGATION_MANDATORY、PROPAGATION_NEVER和PROPAGATION_NESTED。每个传播行为的特点和应用场景通过实例进行了说明,尤其重点讨论了嵌套事务和异常处理对事务的影响。

前言

当我们调用一个基于Spring的Service接口方法时,它将运行于Spring管理的事务环境中,Service接口方法可能会在内部调用其它的Service接口方法以共同完成一个完整的业务操作,因此就会产生服务接口方法嵌套调用的情况, Spring通过事务传播行为控制当前的事务如何传播到被嵌套调用的目标服务接口方法中。
事务传播是Spring进行事务管理的重要概念,其重要性怎么强调都不为过。但是事务传播行为也是被误解最多的地方,在本文里,我们将详细分析不同事务传播行为的表现形式,掌握它们之间的区别。

注:本文可以与上篇文章一同参考
MySQL事务的四个隔离级别详解


Spring的事务传播行为

  1. PROPAGATION_REQUIRED:如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。
  2. PROPAGATION_SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行。
  3. PROPAGATION_MANDATORY:使用当前的事务,如果当前没有事务,就抛出异常。
  4. PROPAGATION_REQUIRES_NEW:新建事务,如果当前存在事务,把当前事务挂起。
  5. PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
  6. PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
  7. PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。

注意:

1、开启PROPAGATION_NESTED传播行为时,需要开启nestedTransactionAllowed属性为true。
   即支持事务的savepoint操作。
2、nestedTransactionAllowed默认为false。
3、JDK要在1.4+
<bean id="transactionManager" 
        class="org.springframework.orm.hibernate4.HibernateTransactionManager">  
        <property name="sessionFactory" ref="sessionFactory"/> 
        <!-- 开启 PROPAGATION_NESTED 事务传播行为  -->
        <property name="nestedTransactionAllowed" value="true"/> 
</bean>

下面提一些基本知识

  1. 事务传播机制只适用于不同bean之间方法的调用,如果一个bean中的两个方法互相调用并不会使用到事务传播。
  2. 事务方法里如果抛RuntimeException,则会导致所有相关事务回滚,个别事务传播机制有点特殊,我们下面会讲到。
  3. 事务方法里如果抛Throwable或者Exception,默认不会导致相关事务回滚,一般都会在出异常的地方提交,就有可能出现部分提交的问题。但可以配置rollback-for属性来控制。

下面我们来详细看一下七个Spring事务传播行为

@Service
public class BaseServiceImpl implements BaseService {

    @Autowired
    private BaseHibernateDao baseHibernateDao;

    @Autowired
    private LoginService loginService;

    //外部事务
    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public Serializable save1(LoginUser entity) throws Exception {
        Serializable save = null;
        loginService.save2(new LoginUser());
        return save;
    }
}
@Service
public class LoginServiceImpl implements LoginService {

    @Autowired
    private BaseHibernateDao baseHibernateDao;

    //内部事务
    @Override
    @Transactional 
    public Serializable save2(LoginUser entity) throws Exception {
        Serializable save = null;
        entity.setUsername("save2");
        save = baseHibernateDao.save(entity);
        return save;
    }
}


@Transactional(propagation = Propagation.SUPPORTS)
支持当前事务,如果当前没有事务,就以非事务方式执行。

//外部事务
@Override
@Transactional(propagation = Propagation.SUPPORTS)  
//支持当前事务,如果当前没有事务,就以非事务方式执行。
public Serializable save1(LoginUser entity) throws Exception {
    Serializable save = null;
    loginService.save2(new LoginUser());
    return save;
}

//内部事务
@Override
public Serializable save2(LoginUser entity) throws Exception {
    Serializable save = null;
    entity.setUsername("save2");
    save = baseHibernateDao.save(entity);
    //空指针异常
    Object obj = null;
    obj.equals("");
    entity =  new LoginUser();
    entity.setUsername("save3");
    save = baseHibernateDao.save(entity);
    return save;
}

以上在内部事务中我们使用了SUPPORTS事务传播行为,以非事务方式执行(可执行增删改操作,但是不受事务控制),虽然中间抛出了空指针异常,但是不会导致第一次save回滚,第二次save失败

1


@Transactional(propagation = Propagation.REQUIRES_NEW)
新建事务,如果当前存在事务,把当前事务挂起。

//外部事务
@Override
@Transactional(propagation = Propagation.REQUIRED)
public Serializable save1(LoginUser entity) throws Exception {
    Serializable save = null;
    loginService.save2(new LoginUser());
    //空指针异常
    Object obj = null;
    obj.equals("");
    return save;
}

//内部事务
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)  
//开启内部事务,执行完毕后会立即提交内部事务
public Serializable save2(LoginUser entity) throws Exception {
    Serializable save = null;
    entity.setUsername("save2");
    save = baseHibernateDao.save(entity);
    return save;
}

外部事务中我们先执行了内部事务,随后外部事务抛出异常,但并不影响内部事务的提交。

3

如果内部事务传播行为为:REQUIRES_NEW,外部事务传播行为为:REQUIRED,若内部事务抛出异常,会影响外部事务吗?

//外部事务
@Override
@Transactional(propagation = Propagation.REQUIRED)
public Serializable save1(LoginUser entity) throws Exception {
    Serializable save = null;
    this.save(new LoginUser("save1"));
    loginService.save2(new LoginUser());
    return save;
}

//内部事务
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)  
public Serializable save2(LoginUser entity) throws Exception {
    Serializable save = null;
    entity.setUsername("save2");
    save = baseHibernateDao.save(entity);
    //空指针异常
    Object obj = null;
    obj.equals("");
    return save;
}

我们来看下执行结果

4

1、若内部事务抛出异常,并且没有处理的话,异常会跑到外部事务中,导致整个事务回滚,不做数据操作
2、若要想让内部事务不影响外部事务,需要在 [内部事务] 中做异常处理,若在外部事务中单独对引用的内部事务做事务回滚,将导致整个事务回滚(包括外部事务)

以下将导致整个事务回滚

//外部事务
@Override
@Transactional(propagation = Propagation.REQUIRED)
public Serializable save1(LoginUser entity) throws Exception {
    Serializable save = null;
    this.save(new LoginUser("save1"));
    try {
        //引用内部事务方法
        loginService.save2(new LoginUser());
    } catch (Exception e) {
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
    }
    return save;
}

以下会单独将内部事务回滚

//内部事务
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public Serializable save2(LoginUser entity) throws Exception {
    Serializable save = null;
    try {
        entity.setUsername("save2");
        save = baseHibernateDao.save(entity);
        //空指针异常
        Object obj = null;
        obj.equals("");
    } catch (Exception e) {
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
    }
    return save;
}


@Transactional(propagation = Propagation.NESTED)
它是已经存在事务的一个真正的子事务。嵌套事务开始执行时,它将取得一个 savepoint。如果这个嵌套事务失败, 我们将回滚到此 savepoint。嵌套事务是外部事务的一部分, 只有外部事务结束后它才会被提交。

//外部事务
@Override
@Transactional(propagation = Propagation.REQUIRED)
public Serializable save1(LoginUser entity) throws Exception {
    Serializable save = null;
    this.save(new LoginUser("save1"));
    loginService.save2(new LoginUser());
    //空指针异常
    Object obj = null;
    obj.equals("");
    return save;
}

//内部事务
@Override
@Transactional(propagation = Propagation.NESTED)    
//开启内部事务,内部事务"嵌套"在外部事务中
public Serializable save2(LoginUser entity) throws Exception {
    Serializable save = null;
    entity.setUsername("save2");
    save = baseHibernateDao.save(entity);
    return save;
}

5

NESTED:虽然开启了一个新的事务,实质是”嵌套”在外部事务中,它的原理是通过savepoint实现的。所以当外部事务发生异常时,会导致整个内外部事务回滚。

注:
与REQUIRES_NEW不同的是,REQUIRES_NEW外部事务异常,内部事务执行正常操作时,外部事务回滚,内部事务提交不回滚

问题:若内部事务发生了异常,并且在内部事务中做了事务回滚,会影响外部事务吗?

//外部事务
@Override
@Transactional(propagation = Propagation.REQUIRED)
public Serializable save1(LoginUser entity) throws Exception {
  Serializable save = null;
  this.save(new LoginUser("save1"));
  loginService.save2(new LoginUser());
  return save;
}

//内部事务
@Transactional(propagation = Propagation.NESTED)
public Serializable save2(LoginUser entity) throws Exception {
    Serializable save = null;
    try {
        entity.setUsername("save2");
        save = baseHibernateDao.save(entity);
        //空指针异常
        Object obj = null;
        obj.equals("");
    } catch (Exception e) {
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
    }
    return save;
}

7

当内部事务发生异常,并且做了事务回滚,不会影响到外部事务,原因为该事务是通过savepoint控制的,不会影响外部事务。


@Transactional(propagation = Propagation.NOT_SUPPORTED)
//以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。

//外部事务
@Override
@Transactional(propagation = Propagation.REQUIRED)
public Serializable save1(LoginUser entity) throws Exception {
    Serializable save = null;
    this.save(new LoginUser("save1"));
    loginService.save2(new LoginUser());
    return save;
}

//内部事务
@Override
@Transactional(propagation = Propagation.NOT_SUPPORTED) 
//以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
public Serializable save2(LoginUser entity) throws Exception {
    Serializable save = null;
    entity.setUsername("save2");
    save = baseHibernateDao.save(entity);
    //空指针异常
    Object obj = null;
    obj.equals("");
    return save;
}

5

以非事务方式执行(可执行增删改操作,但是不受事务控制),所以当内部事务抛出异常后,导致外部事务回滚,但是内部事务能过成功插入一条,并且内部事务没有回滚操作。

注:内部事务能手动将事务回滚吗?

//内部事务
@Override
@Transactional(propagation = Propagation.NOT_SUPPORTED) 
//以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
public Serializable save2(LoginUser entity) throws Exception {
    Serializable save = null;
    try {
        entity.setUsername("save2");
        save = baseHibernateDao.save(entity);
        //空指针异常
        Object obj = null;
        obj.equals("");
    } catch (Exception e) {
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
    }
    return save;
}

6

我们在内部事务中做了异常处理并且手动启动事务回滚,发现内部事务并不能回滚,所以这种情况下内部事务是不能回滚的。

以上我们详细说明了Spring的四个事务传播行为,其他三个较为简单,这里就不做详细说明了。


上面我们提到了,若在两个不同的Bean之间调用是存在事务的传播行为的,那么在同一个Bean之间调用两个方法,事务是怎样的呢?请看如下示例

@Service
public class BaseServiceImpl implements BaseService {

    public BaseServiceImpl () {

    }

    @Autowired
    private BaseHibernateDao baseHibernateDao;

    @Autowired
    private LoginService loginService;


    @Override
    public Serializable save(Object entity) throws Exception {
        return baseHibernateDao.save(entity);
    }

    //外部事务
    @Override
    @Transactional(propagation = Propagation.SUPPORTS)
    public Serializable save1(LoginUser entity) throws Exception {
      Serializable save = null;
      try {
          this.save2(new LoginUser());
          this.save(new LoginUser("save1"));
          //空指针异常
          Object obj = null;
          obj.equals("");
      } catch (Exception e) {
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
      }
      return save;
    }

    //内部事务
    @Override
    @Transactional(propagation = Propagation.REQUIRED)  
    public Serializable save2(LoginUser entity) throws Exception {
        Serializable save = null;
        try {
            entity.setUsername("save2");
            save = baseHibernateDao.save(entity);
            //空指针异常
            Object obj = null;
            obj.equals("");
        } catch (Exception e) {
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        }
        return save;
    }
}

当我们执行了save1方法后,我们来看下数据库

8

当save1调用save2了之后,虽然save2的事务传播行为为REQUIRED,但是我们发现save2并没有执行回滚操作,显然save2的事务传播行为依然为SUPPORTS。

结论:若在同一个Bean之间方法相互调用,以第一个被调用的方法的事务传播行为为基准。

评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值