Spring 事务的传播行为

前言

Spring在TransactionDefinition接口中规定了7种类型的事务传播行为。事务传播行为是Spring框架独有的事务增强特性,他不属于事务实际提供方数据库的行为。

 

什么是事务传播行为

事务传播行为: 用来描述由某一个事务传播行为修饰的方法被嵌套进另一个方法的时事务如何传播。

1
2
3
4
5
6
7
8
9
public void methodA(){
    methodB();
    //doSomething
 }
 
 @Transaction(Propagation=XXX)
 public void methodB(){
    //doSomething
 }

代码中methodA()方法嵌套调用了methodB()方法,methodB()的事务传播行为由@Transaction(Propagation=XXX)设置决定。这里需要注意的是methodA()并没有开启事务,某一个事务传播行为修饰的方法并不是必须要在开启事务的外围方法中调用。

 

Spring中七种事务传播行为

  • PROPAGATION_REQUIRED: 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。Spring默认事务传播
  • PROPAGATION_SUPPORTS: 支持当前事务,如果当前没有事务,就以非事务方式执行。
  • PROPAGATION_MANDATORY: 使用当前的事务,如果当前没有事务,就抛出异常。
  • PROPAGATION_REQUIRES_NEW: 新建事务,如果当前存在事务,把当前事务挂起。
  • PROPAGATION_NOT_SUPPORTED: 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
  • PROPAGATION_NEVER: 以非事务方式执行,如果当前存在事务,则抛出异常。
  • PROPAGATION_NESTED: 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。

 

代码验证

REQUIRED

1
2
3
4
5
6
7
8
9
10
11
// ServiceA
@Transactional(rollbackFor = AALException.class)
public void methodA002() throws AALException {
    // goods 如果替换为除了 apple 之外的物品 订单和库存扣减都正常
    String goods = "orange";
    Order order = Order.builder().orderNo(SDF.format(new Date())).goods(goods).valid(1).createTime(SDF1.format(new Date())).build();

    orderJPA.save(order);
    /** 如果捕获到方法 {@link ServiceB#methodRequired} 的 AALException异常, 直接处理异常则提交订单, 如果抛出异常则回滚订单表的记录 */
    serviceB.methodRequired(order.getGoods());
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**  ServiceB
  *  {@link Propagation#REQUIRED}: 支持当前事务, 如果当前没有事务, 就新建一个事物. (spring默认事务传播)
  * */
//    @Transactional(rollbackFor = AALException.class, propagation = Propagation.REQUIRED)
@Transactional
public void methodRequired(String goods) throws AALException {
    int cnt = stockMapper.reduceStock(goods, 1);

    log.info("reduce stock goods[{}], cnt[{}]", goods, cnt);

    if ("apple".equals(goods)) {
        throw new AALException("update "+ goods +" stock error, return cnt: " + cnt);
    }
}

REQUIRES_NEW

1
2
3
4
5
6
7
8
9
10
11
// ServiceA
@Transactional(rollbackFor = AALException.class)
public void methodA003() throws AALException {
    // goods 如果替换为除了 apple 之外的物品 订单和库存扣减都正常
    String goods = "apple";
    Order order = Order.builder().orderNo(SDF.format(new Date())).goods(goods).valid(1).createTime(SDF1.format(new Date())).build();

    orderJPA.save(order);
    /** 如果捕获到方法 {@link ServiceB#methodRequiresNew} 的 AALException异常, 直接处理异常则提交订单, 如果抛出异常则回滚订单表的记录 */
    serviceB.methodRequiresNew(order.getGoods());
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**  ServiceB
  * {@link Propagation#REQUIRES_NEW}: 新建事务, 如果当前存在事务, 把当前事务挂起.
  *              新建的事物将和被挂起的事务没有任何关系, 是两个独立的事物,
  *              外层事务失败回滚之后, 不能回滚内层事务执行的结果.
  *              内层事务失败抛出异常, 外层事务捕获, 也可以不处理回滚操作
  * */
@Transactional(rollbackFor = AALException.class, isolation = Isolation.DEFAULT, propagation = Propagation.REQUIRES_NEW)
public void methodRequiresNew(String goods) throws AALException {
    int cnt = stockMapper.reduceStock(goods, 1);

    log.info("reduce stock goods[{}], cnt[{}]", goods, cnt);
    // 如果商品是 apple, 则抛出 AALException, 并回滚扣除库存的更新操作
    if ("apple".equals(goods)) {
        throw new AALException("update "+ goods +" stock error, return cnt: " + cnt);
    }
}

MANDATORY

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// ServiceA
@Transactional(rollbackFor = AALException.class)
public void methodA004() throws AALException {
    // goods 如果替换为除了 apple 之外的物品 订单和库存扣减都正常
    String goods = "apple";
    Order order = Order.builder().orderNo(SDF.format(new Date())).goods(goods).valid(1).createTime(SDF1.format(new Date())).build();

    try {
        orderJPA.save(order);
        serviceB.methodMandatory(order.getGoods());
    } catch (AIException e) {
        throw new AALException(e.getMessage());
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**  ServiceB
  * {@link Propagation#MANDATORY}: 支持当前事务, 如果当前没有事务, 则抛出 {@link org.springframework.transaction.IllegalTransactionStateException} 异常
  *      即: 如果方法 ServiceA#methodA004 没有声明事务, 则会抛出异常
  *
  *      如果不指定 rollbackFor 则延用当前已有事务的 rollbackFor, 即: ServiceA#methodA004 声明事务的rollbackFor
  * */
@Transactional(rollbackFor = AIException.class, propagation = Propagation.MANDATORY)
public void methodMandatory(String goods) throws AIException {
    int cnt = stockMapper.reduceStock(goods, 1);
    log.info("reduce stock goods[{}], cnt[{}]", goods, cnt);

    if ("apple".equals(goods)) {
        throw new AIException("update "+ goods +" stock error, return cnt: " + cnt);
    }
}

SUPPORTS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// ServiceA
@Transactional(rollbackFor = AALException.class)
public void methodA005() throws AALException {
    // 在方法 methodA005 设置了事务的前提: goods 如果替换为除了 apple 之外的物品 订单和库存扣减都正常
    String goods = "apple";
    Order order = Order.builder().orderNo(SDF.format(new Date())).goods(goods).valid(1).createTime(SDF1.format(new Date())).build();

    try {
        orderJPA.save(order);
        serviceB.methodSupports(order.getGoods());
    } catch (AALException e) {
        throw new AALException(e.getMessage());
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
/**  ServiceB
  *  {@link Propagation#SUPPORTS}: 支持当前事务, 如果当前没有事务, 则以非事务方式执行
  * */
@Transactional(rollbackFor = AALException.class, propagation = Propagation.SUPPORTS)
public void methodSupports(String goods) throws AALException {
    int cnt = stockMapper.reduceStock(goods, 1);
    log.info("reduce stock goods[{}], cnt[{}]", goods, cnt);

    if ("apple".equals(goods)) {
        throw new AALException("update "+ goods +" stock error, return cnt: " + cnt);
    }
}

NOT_SUPPORTED

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// ServiceA
@Transactional(rollbackFor = AALException.class)
public void methodA006() {
    String goods = "apple";
    Order order = Order.builder().orderNo(SDF.format(new Date())).goods(goods).valid(1).createTime(SDF1.format(new Date())).build();

    try {
        orderJPA.save(order);
        serviceB.methodNotSupported(order.getGoods());
    } catch (AALException e) {
        System.out.println(e.getMessage());
        // 此处如果改为将异常抛出, 则订单提交记录会被回滚
        //            throw new AALException(e.getMessage());
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
/**  ServiceB
  *  {@link Propagation#NOT_SUPPORTED}: 以非事务方式执行操作, 如果当前存在事务, 就把当前事务挂起
  * */
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void methodNotSupported(String goods) throws AALException {
    int cnt = stockMapper.reduceStock(goods, 1);
    log.info("reduce stock goods[{}], cnt[{}]", goods, cnt);

    // 由于 NOT_SUPPORTED 以非事务运行, 所以此处抛异常并不会影响库存的扣减, 即: 并不会回滚库存扣减操作
    if ("apple".equals(goods)) {
        throw new AALException("update "+ goods +" stock error, return cnt: " + cnt);
    }
}

NEVER

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// ServiceA
@Transactional(rollbackFor = AALException.class)
public void methodA007() throws AALException {
    String goods = "apple";
    Order order = Order.builder().orderNo(SDF.format(new Date())).goods(goods).valid(1).createTime(SDF1.format(new Date())).build();

    try {
        orderJPA.save(order);
        // 因为 methodNever 是在 NEVER 下执行的, 所以 methodA007 方法必须不能有事务
        serviceB.methodNever(order.getGoods());
    } catch (AALException e) {
        System.out.println(e.getMessage());
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
/**  ServiceB
  *  {@link Propagation#NEVER}: 以非事务方式执行, 如果当前存在事务, 则抛出异常 {@link org.springframework.transaction.IllegalTransactionStateException}
  * */
@Transactional(propagation = Propagation.NEVER)
public void methodNever(String goods) throws AALException {
    int cnt = stockMapper.reduceStock(goods, 1);
    log.info("reduce stock goods[{}], cnt[{}]", goods, cnt);

    // NEVER 以非事务执行, 因此是否抛出异常都不会回滚
    if ("apple".equals(goods)) {
        throw new AALException("update "+ goods +" stock error, return cnt: " + cnt);
    }
}

NESTED

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// ServiceA
@Transactional(rollbackFor = AALException.class)
public void methodA008() throws AALException {
    String goods = "apple";

    try {
        jdbcTemplate.execute("INSERT INTO aal_order(order_no, goods, valid, create_time) VALUES ('"+ SDF.format(new Date()) +"', '"+ goods +"', 1, '"+ SDF1.format(new Date()) +"')");
        serviceB.methodNested(goods);
        throw new AALException("throw exception for aalException");
    } catch (AALException e) {
        // goods 改为 apple 并且此处不进行抛出异常(注释掉以下抛出异常代码), 则 内部事务(methodNested)记录会回滚, 外部事务(methodA008)记录会提交
        throw new AALException(e.getMessage());
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**  ServiceB
  *  {@link Propagation#NESTED}: 如果一个活动的事务存在, 则运行在一个嵌套的事务中.
  *              如果没有活动事务, 则按{@link Propagation#REQUIRED}属性执行.
  *              它使用了一个单独的事务, 这个事务拥有多个可以回滚的保存点.
  *              内部事务的回滚不会对外部事务造成影响.
  *              它只对{@link org.springframework.jdbc.datasource.DataSourceTransactionManager}事务管理器起效
  * */
@Transactional(rollbackFor = AALException.class, propagation = Propagation.NESTED)
public void methodNested(String goods) throws AALException {
    int cnt = stockMapper.reduceStock(goods, 1);
    log.info("reduce stock goods[{}], cnt[{}]", goods, cnt);

    if ("apple".equals(goods)) {
        throw new AALException("update "+ goods +" stock error, return cnt: " + cnt);
    }
}
1
2
3
4
5
6
7
/**
  * 配置事务管理 测试方法 {@link com.answer.issues.integration.spring.tx.ServiceB#methodNested}时需要修改为 {@link DataSourceTransactionManager}
  * */
@Bean("transactionManager")
public PlatformTransactionManager annotationDrivenTransactionManager(@Qualifier("dataSource") DataSource dataSource) {
    return new DataSourceTransactionManager(dataSource);
}

 

NESTED & REQUIRES_NEW 区别

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

  • **REQUIRES_NEW: ** 启动一个新的, 不依赖于环境的 “内部” 事务。这个事务将被完全 commited 或 rollback 而不依赖于外部事务, 它拥有自己的隔离范围, 自己的锁, 等等。当内部事务开始执行时, 外部事务将被挂起, 内务事务结束时, 外部事务将继续执行。

结论: REQUIRES_NEW 完全是一个新的事务。而 NESTED 则是外部事务的子事务, 如果外部事务 commit, 嵌套事务也会被 commit, 外部事务 rollback, 嵌套事务也会被 rollback。当嵌套事务异常回滚时不会对外部事务造成影响, 即: 外部事务可以正常提交事务.(如果外部事务 commit, 嵌套事务也会被 commit(前提: 嵌套事务没有出现异常的情况))

说明: NESTED 只是Spring针对JDBC3.0以上版本SavePoint机制的一个事务传播机制的扩展

 

参考网址