大家好,我是一名CV工程师。

在使用Spring声明式事务时,你是否曾有过这样的困惑:明明标注了@Transactional,但事务却没按你预想的那样回滚?在复杂的业务方法嵌套调用中,事务的边界为何总像“玄学”一样难以捉摸?其实根本原因是:Spring事务的传播机制有着清晰、严谨的规则。虽然我们平时用到传播机制并不多。但是理解它,并非为了炫技,而是为了让我们能能精准控制数据一致性,也是为了在面试过程中能清楚的回答出面试官关于声明式事务传播机制的问题。

声明式事务传播机制的核心概念

基本定义与核心概念

传播机制是Spring声明式事务管理的核心特性之一,用于定义在多个事务方法相互调用时,事务应该如何传播和处理边界。特别需要注意的是传播行为是定义被调用方法的事务行为,而不是调用方法的控制手段。所以传播机制是需要定义在被调用方法上,以用来控制该方法如何与调用方的事务上下文进行交互。

七种传播传播行为区别

传播行为行为定义简单描述(以被调用方视角)
REQUIREDSpring默认的传播机制。如果当前(调用方)存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。REQUIRED传播机制最常用的情况是在一个事务重进行多个操作,要么全部成功,要么全部失败。如果其中一个操作失败,整个事务都将被回滚。"我"需要一个事务,有就加入,没有就新建
SUPPORTS当前方法如果在一个事务方法中被调用,则加入该事务;否则,已非事务的方式运行。SUPPORTS传播机制适用于对事务要求不高的操作,例如读取操作。"我"支持事务,有就用,没有就不用
MANDATORY传播机制表示当前方法必须在一个事务中被调用,否则将抛出异常。MANDATORY传播机制适用于在需要事务的情况下调用方法。"我"必须在事务中运行,否则抛异常
REQUIRES_NEW表示当前方法必须开启一个新事务运行,如果当前存在事务,则挂起该事务。REQUIRES_NEW传播机制适用于对事务要求比较高的操作,例如更新操作。REQUIRES_NEW修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。"我"需要新事务,有就挂起旧的,没有就新建
NOT_SUPPORTED以非事务方式运行,如果当前存在事务,则把当前事务挂起。"我"不支持事务,有就挂起,没有就算了
NEVER以非事务方式运行,如果当前存在事务,则抛出异常。"我"禁止在事务中运行,否则抛异常
NESTED如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于PROPAGATION_REQUIRED。"我"需要嵌套事务,有就用保存点,没有就新建

代码验证各个传播行为

调用方使用@Transactional注解,被调用方没有使用

 @Service("test1Service")
public class Test1Service {

    @Resource
    private Test2Service test2Service;

    @Resource
    private Test1Mapper test1Mapper;

    /**
     * 调用方法
     */
    @Transactional(rollbackFor = Exception.class)
    public void update() {
        test1Mapper.updateId1ColTo2();//更新被回滚
        test2Service.update();
    }
}


@Service("test2Service")
public class Test2Service {

    @Resource
    private Test2Mapper test2Mapper;

    /**
     * 被调用方法
     */
    public void update(){
        test2Mapper.updateId1ColTo2();//更新被回滚
        throw new RuntimeException("模拟异常");
    }
}


/**
  * 结论:被调用方法受调用方法的事务管控
  */

调用方未使用@Transactional注解,被调用方使用

 @Service("test1Service")
public class Test1Service {

    @Resource
    private Test2Service test2Service;

    @Resource
    private Test1Mapper test1Mapper;

    /**
     * 调用方法
     */
    public void update() {
        test1Mapper.updateId1ColTo2();//更新未被回滚
        test2Service.update();
    }
}


@Service("test2Service")
public class Test2Service {

    @Resource
    private Test2Mapper test2Mapper;

    /**
     * 被调用方法
     */
     @Transactional(rollbackFor = Exception.class)
    public void update(){
        test2Mapper.updateId1ColTo2();//更新被回滚
        throw new RuntimeException("模拟异常");
    }
}


/**
  * 结论:调用方法不受被调用方的事务管控,被调用方法独立使用事务。
  */

被调用方法使用REQUIRED传播行为

调用方法有事务
@Service("test1Service")
public class Test1Service {

    @Resource
    private Test2Service test2Service;

    @Resource
    private Test1Mapper test1Mapper;

    /**
     * 调用方法
     */
    @Transactional(rollbackFor = Exception.class)
    public void update() {
        test1Mapper.updateId1ColTo2();//更新被回滚
        test2Service.update();
        throw new RuntimeException("模拟异常");
    }
}

@Service("test2Service")
public class Test2Service {

    @Resource
    private Test2Mapper test2Mapper;

    /**
     * 被调用方法
     */
    @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
    public void update() {
        test2Mapper.updateId1ColTo2();//更新被回滚
    }
}

/**
 *现象:调用方法中抛出异常,被调用方法中的更新也被回滚。
 *结论:被调用方法加入到调用方法事务中。
 */
调用方法无事务
@Service("test1Service")
public class Test1Service {

    @Resource
    private Test2Service test2Service;

    @Resource
    private Test1Mapper test1Mapper;

    /**
     * 调用方法
     */
//    @Transactional(rollbackFor = Exception.class)
    public void update() {
        test1Mapper.updateId1ColTo2();//更新未被回滚
        test2Service.update();
    }
}

@Service("test2Service")
public class Test2Service {

    @Resource
    private Test2Mapper test2Mapper;

    /**
     * 被调用方法
     */
    @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
    public void update() {
        test2Mapper.updateId1ColTo2();//更新被回滚
        throw new RuntimeException("模拟异常");
    }
}

/**
 *现象:被调用方法的更新被回滚
 *结论:被调用方法的创建了新的事务
 */

被调用方法使用SUPPORTS传播行为

调用方法有事务
@Service("test1Service")
public class Test1Service {

    @Resource
    private Test2Service test2Service;

    @Resource
    private Test1Mapper test1Mapper;

    /**
     * 调用方法
     */
    @Transactional(rollbackFor = Exception.class)
    public void update() {
        test1Mapper.updateId1ColTo2();//更新被回滚
        test2Service.update();
        throw new RuntimeException("模拟异常");
    }
}

@Service("test2Service")
public class Test2Service {

    @Resource
    private Test2Mapper test2Mapper;

    /**
     * 被调用方法
     */
    @Transactional(rollbackFor = Exception.class,propagation = Propagation.SUPPORTS)
    public void update() {
        test2Mapper.updateId1ColTo2();//更新被回滚
    }
}

/**
 *现象:调用方法中抛出异常,被调用方法中的更新也被回滚。
 *结论:被调用方法加入到调用方法事务中。次情景下与REQUIRED传播行为一致。
 */
调用方法无事务
@Service("test1Service")
public class Test1Service {

    @Resource
    private Test2Service test2Service;

    @Resource
    private Test1Mapper test1Mapper;

    /**
     * 调用方法
     */
//    @Transactional(rollbackFor = Exception.class)
    public void update() {
        test1Mapper.updateId1ColTo2();//更新未被回滚
        test2Service.update();
    }
}

@Service("test2Service")
public class Test2Service {

    @Resource
    private Test2Mapper test2Mapper;

    /**
     * 被调用方法
     */
    @Transactional(rollbackFor = Exception.class,propagation = Propagation.SUPPORTS)
    public void update() {
        test2Mapper.updateId1ColTo2();//更新未被回滚
        throw new RuntimeException("模拟异常");
    }
}

/**
 *现象:被调用方法的更新未被回滚
 *结论:被调用方法使用SUPPORTS传播行为时,当调用方法未开启事务时,被调用方法已非事务方式运行
 */

被调用方法使用MANDATORY传播行为

调用方法有事务
@Service("test1Service")
public class Test1Service {

    @Resource
    private Test2Service test2Service;

    @Resource
    private Test1Mapper test1Mapper;

    /**
     * 调用方法
     */
    @Transactional(rollbackFor = Exception.class)
    public void update() {
        test1Mapper.updateId1ColTo2();//更新被回滚
        test2Service.update();
        throw new RuntimeException("模拟异常");
    }
}

@Service("test2Service")
public class Test2Service {

    @Resource
    private Test2Mapper test2Mapper;

    /**
     * 被调用方法
     */
    @Transactional(rollbackFor = Exception.class,propagation = Propagation.MANDATORY)
    public void update() {
        test2Mapper.updateId1ColTo2();//更新被回滚
    }
}

/**
 *现象:调用方法中抛出异常,被调用方法中的更新也被回滚。
 *结论:被调用方法加入到调用方法事务中。此情景下与REQUIRED传播行为一致。
 */
调用方法无事务
@Service("test1Service")
public class Test1Service {

    @Resource
    private Test2Service test2Service;

    @Resource
    private Test1Mapper test1Mapper;

    /**
     * 调用方法
     */
//    @Transactional(rollbackFor = Exception.class)
    public void update() {
        test1Mapper.updateId1ColTo2();
        test2Service.update();
    }
}

@Service("test2Service")
public class Test2Service {

    @Resource
    private Test2Mapper test2Mapper;

    /**
     * 被调用方法
     */
    @Transactional(rollbackFor = Exception.class,propagation = Propagation.MANDATORY)
    public void update() {
        test2Mapper.updateId1ColTo2();
        throw new RuntimeException("模拟异常");
    }
}

/**
 *现象:运行代码报错:No existing transaction found for transaction marked with propagation 'mandatory'
 *结论:被调用方法使用MANDATORY传播行为时,调用方法必须开启事务,否则运行报错
 */

被调用方法使用REQUIRES_NEW传播行为

调用方法有事务
@Service("test1Service")
public class Test1Service {

    @Resource
    private Test2Service test2Service;

    @Resource
    private Test1Mapper test1Mapper;

    /**
     * 调用方法
     */
    @Transactional(rollbackFor = Exception.class)
    public void update() {
        test1Mapper.updateId1ColTo2();//更新被回滚
        test2Service.update();
        throw new RuntimeException("模拟异常");
    }
}

@Service("test2Service")
public class Test2Service {

    @Resource
    private Test2Mapper test2Mapper;

    /**
     * 被调用方法
     */
    @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRES_NEW)
    public void update() {
        test2Mapper.updateId1ColTo2();//更新未被回滚
    }
}

/**
 *现象:调用方法中抛出异常,被调用方法中的更新未被回滚。
 *结论:被调用方法开启了独立的事务,与调用方法事务隔离。
 */
调用方法无事务
@Service("test1Service")
public class Test1Service {

    @Resource
    private Test2Service test2Service;

    @Resource
    private Test1Mapper test1Mapper;

    /**
     * 调用方法
     */
//    @Transactional(rollbackFor = Exception.class)
    public void update() {
        test1Mapper.updateId1ColTo2();//更新未被回滚
        test2Service.update();
    }
}

@Service("test2Service")
public class Test2Service {

    @Resource
    private Test2Mapper test2Mapper;

    /**
     * 被调用方法
     */
    @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRES_NEW)
    public void update() {
        test2Mapper.updateId1ColTo2();//更新被回滚
        throw new RuntimeException("模拟异常");
    }
}

/**
 *现象:被调用方法的更新被回滚
 *结论:被调用方法使用REQUIRES_NEW传播行为时,当调用方法未开启事务时,被调用方法开启独立事务。此情景下与REQUIRED传播行为一致。
 */

被调用方法使用NOT_SUPPORTED传播行为

调用方法有事务
@Service("test1Service")
public class Test1Service {

    @Resource
    private Test2Service test2Service;

    @Resource
    private Test1Mapper test1Mapper;

    /**
     * 调用方法
     */
    @Transactional(rollbackFor = Exception.class)
    public void update() {
        test1Mapper.updateId1ColTo2();//更新被回滚
        test2Service.update();
    }
}

@Service("test2Service")
public class Test2Service {

    @Resource
    private Test2Mapper test2Mapper;

    /**
     * 被调用方法
     */
    @Transactional(rollbackFor = Exception.class,propagation = Propagation.NOT_SUPPORTED)
    public void update() {
        test2Mapper.updateId1ColTo2();//更新未被回滚
        throw new RuntimeException("模拟异常");
    }
}

/**
 *现象:被调用方法中抛出异常,被调用方法中的更新未被回滚,但调用方法更新被回滚。
 *结论:被调用方法不受事务管控,不管是自己还是调用方法出现异常都不会回滚更新操作。
 */
调用方法无事务
@Service("test1Service")
public class Test1Service {

    @Resource
    private Test2Service test2Service;

    @Resource
    private Test1Mapper test1Mapper;

    /**
     * 调用方法
     */
//    @Transactional(rollbackFor = Exception.class)
    public void update() {
        test1Mapper.updateId1ColTo2();//更新未被回滚
        test2Service.update();
    }
}

@Service("test2Service")
public class Test2Service {

    @Resource
    private Test2Mapper test2Mapper;

    /**
     * 被调用方法
     */
    @Transactional(rollbackFor = Exception.class,propagation = Propagation.NOT_SUPPORTED)
    public void update() {
        test2Mapper.updateId1ColTo2();//更新未被回滚
        throw new RuntimeException("模拟异常");
    }
}

/**
 *现象:调用方法与被调用方法的更新均被回滚
 *结论:被调用方法使用NOT_SUPPORTED传播行为时,当调用方法未开启事务时,被调用方法以非事务方式运行。此情景下与不使用声明式事务现象一致。
 */

被调用方法使用NEVER传播行为

调用方法有事务
@Service("test1Service")
public class Test1Service {

    @Resource
    private Test2Service test2Service;

    @Resource
    private Test1Mapper test1Mapper;

    /**
     * 调用方法
     */
    @Transactional(rollbackFor = Exception.class)
    public void update() {
        test1Mapper.updateId1ColTo2();
        test2Service.update();
        throw new RuntimeException("模拟异常");
    }
}

@Service("test2Service")
public class Test2Service {

    @Resource
    private Test2Mapper test2Mapper;

    /**
     * 被调用方法
     */
    @Transactional(rollbackFor = Exception.class,propagation = Propagation.NEVER)
    public void update() {
        test2Mapper.updateId1ColTo2();
    }
}

/**
 *现象:代码出现异常:Existing transaction found for transaction marked with propagation 'never'
 *结论:被调用方法必须被未开启事务的方法调用,否则异常。
 */
调用方法无事务
@Service("test1Service")
public class Test1Service {

    @Resource
    private Test2Service test2Service;

    @Resource
    private Test1Mapper test1Mapper;

    /**
     * 调用方法
     */
//    @Transactional(rollbackFor = Exception.class)
    public void update() {
        test1Mapper.updateId1ColTo2();//更新未被回滚
        test2Service.update();
    }
}

@Service("test2Service")
public class Test2Service {

    @Resource
    private Test2Mapper test2Mapper;

    /**
     * 被调用方法
     */
    @Transactional(rollbackFor = Exception.class,propagation = Propagation.NEVER)
    public void update() {
        test2Mapper.updateId1ColTo2();//更新未被回滚
        throw new RuntimeException("模拟异常");
    }
}

/**
 *现象:调用方法与被调用方法的更新均未被回滚
 *结论:被调用方法使用NEVER传播行为时,当调用方法未开启事务时,被调用方法以非事务方式运行。此情景下与不使用声明式事务现象一致。
 */

被调用方法使用NESTED传播行为

调用方法有事务
@Service
public class BatchProcessService {
    
    @Transactional(propagation = Propagation.REQUIRED)
    public void batchProcessOrders(List<Order> orders) {
        for (Order order : orders) {
            try {
                // 嵌套事务处理每个订单
                processOrderNested(order);
            } catch (Exception e) {
                // 单个订单失败不影响其他订单
                log.error("订单处理失败: {}", order.getId(), e);
            }
        }
    }
    
    @Transactional(propagation = Propagation.NESTED)
    public void processOrderNested(Order order) {
        // 嵌套事务,可以独立回滚
        validateOrder(order);
        deductInventory(order);
        createPayment(order);
        
        if (hasError(order)) {
            throw new RuntimeException("订单处理失败");
        }
    }
}

/**
 *现象:单个循环内的订单出现异常,不影响其他订单的更新提交
 *结论:如果当前存在事务,则在嵌套事务内执行(保存点机制);如果不存在,则创建新事务。
 */
调用方法无事务
@Service("test1Service")
public class Test1Service {

    @Resource
    private Test2Service test2Service;

    @Resource
    private Test1Mapper test1Mapper;

    /**
     * 调用方法
     */
//    @Transactional(rollbackFor = Exception.class)
    public void update() {
        test1Mapper.updateId1ColTo2();//更新未被回滚
        test2Service.update();
    }
}

@Service("test2Service")
public class Test2Service {

    @Resource
    private Test2Mapper test2Mapper;

    /**
     * 被调用方法
     */
    @Transactional(rollbackFor = Exception.class,propagation = Propagation.NESTED)
    public void update() {
        test2Mapper.updateId1ColTo2();//更新被回滚
        throw new RuntimeException("模拟异常");
    }
}

/**
 *现象:被调用方法的更新被回滚
 *结论:被调用方法使用NESTED传播行为时,当调用方法未开启事务时,被调用方法开启独立事务。此情景下与REQUIRED传播行为一致。
 */
本站提供的所有下载资源均来自互联网,仅提供学习交流使用,版权归原作者所有。如需商业使用,请联系原作者获得授权。 如您发现有涉嫌侵权的内容,请联系我们 邮箱:alixiixcom@163.com