@Transactional 中的 事务传播属性 propagation 详解

51 阅读9分钟

这里的事务传播属性都是针对于内部方法的事务来说的:

传播行为当前有事务当前无事务异常回滚范围
REQUIRED加入事务新建事务整个事务
SUPPORTS加入事务非事务运行事务内操作
MANDATORY加入事务抛异常整个事务
REQUIRES_NEW新建事务新建事务仅新事务
NOT_SUPPORTED挂起事务,非事务运行非事务运行
NEVER抛异常非事务运行
NESTED嵌套事务(保存点)新建事务嵌套事务内

传播属性含义行为说明是否始终开启新事务外部无事务时
REQUIRED默认值支持当前事务,如果不存在则新建一个新建事务
SUPPORTS支持支持当前事务,如果不存在则以非事务方式执行非事务执行
MANDATORY强制支持当前事务,如果不存在则抛出异常抛异常
REQUIRES_NEW新建挂起当前事务,新建一个独立的新事务新建事务
NOT_SUPPORTED不支持挂起当前事务,以非事务方式执行否(挂起)非事务执行
NEVER永不禁止事务,如果当前存在事务则抛出异常非事务执行
NESTED嵌套在当前事务中创建一个嵌套事务(Savepoint机制)新建事务

注意方法A调用有事务的方法B,需要用Spring 的代理对象,不然事务不生效。因为@Transactional底层是通过代理类实现事务的。

案例展示:

注:
外部方法就是指外层第一个有事务的方法或是发起事务的方法(例子中一般是testxxx) 内部方法就是指 外部有事务的这个方法调用的内部方法(例子中的orderSave)

方法A调用有事务的方法B,需要用Spring 的代理对象,不然事务不生效。因为@Transactional底层是通过代理类实现事务的。


  1. 内部方法都使用:Propagation.REQUIRED

外部内部方法在同一个事务中,谁报错都会同时回滚,都成功才会同时提交;

这案例表现为,book表和 user_order 表同时插入数据或者没有数据;

@Autowired
BookService bookService;

@Override
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
public String testRequired() {
    // 保存 book 表数据
    Book book = new Book();
    book.setName("English");
    book.setCreateTime(LocalDateTime.now());
    bookService.save(book);

    // 这里调用其他有事务的方法
    // 这里一定要获取到代理对象来调用 orderSave,不然orderSave 的事务不生效
    UserOrderService userOrderService = (UserOrderService)AopContext.currentProxy();
    
    // 这里即使 捕捉了异常,外部方法同样也会回滚,因为REQUIRED 来说,两个方法已经放在同一个事务中了
    try {
        userOrderService.orderSave();
    } catch (Exception e) {
        System.out.println(e.getMessage());
    }

    // 模拟错误-回滚,如果是 orderSave 是 Propagation.REQUIRED,那么同一个事务,都会同时回滚
    int a = 0;
    int b = 10 / a;

    return "success";
}

@Override
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
public String orderSave() {
    // case1(当前方法propagation = Propagation.REQUIRED):
    //      如果外部方法 testRequired 已经有事务了,那么就加入外部的事务,相当于就是用同一个事务(提交或是回滚都一起),
    //      如果外部方法 testRequired 没有事务,那么当前方法就自己创建事务,提交或是回滚 都和外部方法无关
    // 保存 user_order 表数据
    UserOrder userOrder = new UserOrder();
    userOrder.setUser("user1");
    userOrder.setAmount(100);

    save(userOrder);
    
    // 模拟错误-回滚,如果是 orderSave 是 Propagation.REQUIRED,那么同一个事务,都会同时回滚
    // int a = 0;
    // int b = 10 / a;

    return "success";
}
  1. 内部方法使用:Propagation.REQUIRES_NEW

外部内部方法在不同的事务中,内部方法失败不会影响外部的事务,反之,如果外部方法报错,也不会影响内部方法的提交,因为是各自独立的事务;

这案例表现为,book表和 user_order 表可以只有一个表插入数据成功,另一个表插入数据失败;


@Override
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES)
public String testRequiredNew() {

    Book book = new Book();
    book.setName("English");
    book.setCreateTime(LocalDateTime.now());
    bookService.save(book);

    // 这里一定要获取到代理对象来调用 orderSave,不然orderSave 的事务不生效
    UserOrderService userOrderService = (UserOrderService)AopContext.currentProxy();
    try {
        // 异常捕捉了,内部方法失败不会影响外部的事务,因为这是两个独立的事务
        // 反之,如果外部方法报错,也不会影响内部方法的提交,因为是各自独立的事务
        userOrderService.orderSave();
    } catch (Exception e) {
        System.out.println(e.getMessage());
    }
    // 模拟错误-回滚
    // int a = 0;
    // int b = 10 / a;

    return "success";
}

@Override
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
public String orderSave() {
    // case2(当前方法propagation = Propagation.REQUIRES_NEW):
    //      不管外部方法有没有事务,当前都会新建一个事务,内部事务和外部事务各不影响,内部事务报错只会回滚内部方法,外部事务还是会正常提交,反之一样
    UserOrder userOrder = new UserOrder();
    userOrder.setUser("user1");
    userOrder.setAmount(100);

    save(userOrder);
    int a = 0;
    int b = 10 / a;

    return "success";
}
  1. 内部方法使用:Propagation.SUPPORTS

    内部方法的事务取决于外部方法的事务:

    如果外部方法有事务,就加入外部方法的事务,用同一个事务(同回滚或提交);

    如果外部方法没有事务,那大家都不用事务,那么内外部方法都不会有事务,就普通方法调用;

// 这里注释了外部方法的事务
// 这里表现为 都没有事务,外部方法 book 表插入了数据, 内部方法 user_order 没有插入数据
@Override
// @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
public String testSupports() {

   Book book = new Book();
   book.setName("English");
   book.setCreateTime(LocalDateTime.now());
   bookService.save(book);

   // 这里一定要获取到代理对象来调用 orderSave,不然orderSave 的事务不生效
   UserOrderService userOrderService = (UserOrderService)AopContext.currentProxy();
   try {
       // 异常捕捉了,内部方法失败不会影响外部的事务,因为这是两个独立的事务
       // 反之,如果外部方法报错,也不会影响内部方法的提交,因为是各自独立的事务
       userOrderService.orderSave();
   } catch (Exception e) {
       System.out.println(e.getMessage());
   }
   // 模拟错误-回滚
   // int a = 0;
   // int b = 10 / a;

   return "success";
}

@Override
@Transactional(rollbackFor = Exception.class, propagation = Propagation.SUPPORTS)
public String orderSave() {
   // case3(当前方法propagation = Propagation.SUPPORTS):
   //      外部方法有事务,则用外部方法的事务,两个方法就在同一个事务中了,同时回滚和提交
   //      外部方法 没有事务,则都没有事务,按普通方法逻辑处理

   UserOrder userOrder = new UserOrder();
   userOrder.setUser("user1");
   userOrder.setAmount(100);

   save(userOrder);
   int a = 0;
   int b = 10 / a;

   return "success";
}
  1. 内部方法使用:Propagation.MANDATORY

    内部方法的事务取决于外部方法的事务:

    外部方法有事务,则用外部方法的事务,两个方法就在同一个事务中了,同时回滚和提交;

    外部方法 没有事务,则内部方法抛异常;

// 这里注释了外部事务,那么调用内部方法的时候,就会抛异常
// 这里 book 表会有数据, user_order 没有数据
@Override
// @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
public String testMandatory() {

    Book book = new Book();
    book.setName("English");
    book.setCreateTime(LocalDateTime.now());
    bookService.save(book);

    // 这里一定要获取到代理对象来调用 orderSave,不然orderSave 的事务不生效
    UserOrderService userOrderService = (UserOrderService)AopContext.currentProxy();

    userOrderService.orderSave();
    // 模拟错误-回滚
    // int a = 0;
    // int b = 10 / a;

    return "success";
}


@Override
@Transactional(rollbackFor = Exception.class, propagation = Propagation.MANDATORY)
public String orderSave() {
    // case4(当前方法propagation = Propagation.MANDATORY):
    //      外部方法有事务,则用外部方法的事务,两个方法就在同一个事务中了,同时回滚和提交
    //      外部方法 没有事务,则内部方法抛异常

    UserOrder userOrder = new UserOrder();
    userOrder.setUser("user1");
    userOrder.setAmount(100);

    save(userOrder);
    int a = 0;
    int b = 10 / a;

    return "success";
}
  1. 内部方法使用:Propagation.NOT_SUPPORTED

    外部方法有事务,则挂起事务,内部方法不参数事务,即使外部方法回滚,也不影响内部方法的数据;

    外部方法 没有事务,就都没有事务,普通方法处理;

// 这里外部方法抛了异常,但是内部方法还是执行了
// 表现为 book 表没有数据,user_order 表有数据
@Override
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
public String testNotSupported() {

    Book book = new Book();
    book.setName("English");
    book.setCreateTime(LocalDateTime.now());
    bookService.save(book);

    // 这里一定要获取到代理对象来调用 orderSave,不然orderSave 的事务不生效
    UserOrderService userOrderService = (UserOrderService)AopContext.currentProxy();

    userOrderService.orderSave();
    // 模拟错误-回滚
    int a = 0;
    int b = 10 / a;

    return "success";
}


@Override
@Transactional(rollbackFor = Exception.class, propagation = Propagation.NOT_SUPPORTED)
public String orderSave() {
    // case5(当前方法propagation = Propagation.NOT_SUPPORTED):
    //      外部方法有事务,则挂起事务,内部方法不参数事务,即使外部方法回滚,也不影响内部方法的数据
    //      外部方法 没有事务,就都没有事务,普通方法处理

    UserOrder userOrder = new UserOrder();
    userOrder.setUser("user1");
    userOrder.setAmount(100);

    save(userOrder);

    return "success";
}
  1. 内部方法使用:Propagation.NEVER

    外部方法有事务,内部方法就会抛出异常;

    外部方法 没有事务,就正常按照普通方法处理;


// 外部方法有事务,这里内部方法配置了 Propagation.NEVER,那么内部方法就会抛异常
// 表现为 book 表没有数据,user_order 表没有数据
@Override
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
public String testNever() {

    Book book = new Book();
    book.setName("English");
    book.setCreateTime(LocalDateTime.now());
    bookService.save(book);

    // 这里一定要获取到代理对象来调用 orderSave,不然orderSave 的事务不生效
    UserOrderService userOrderService = (UserOrderService)AopContext.currentProxy();

    userOrderService.orderSave();
    // 模拟错误-回滚
    // int a = 0;
    // int b = 10 / a;

    return "success";
}


@Override
@Transactional(rollbackFor = Exception.class, propagation = Propagation.NEVER)
public String orderSave() {
    // case6(当前方法propagation = Propagation.NEVER):
    //      外部方法有事务,内部方法就会抛出异常
    //      外部方法 没有事务,就正常按照普通方法处理

    UserOrder userOrder = new UserOrder();
    userOrder.setUser("user1");
    userOrder.setAmount(100);

    save(userOrder);

    return "success";
}
  1. 内部方法使用:Propagation.NESTED

    外部方法事务回滚,会导致内部事务一起回滚;

    内部方法回滚,不会造成外部事务回滚;


// 外部方法有事务,这里内部方法配置了 Propagation.NESTED,那调用的内部方法,作为一个事务中的保存点
// 特点是:内部方法异常回滚,不会让外部方法回滚;但是外部方法回滚,内部方法会一起回滚!
// 表现为 book 表有数据,user_order 表没有数据
@Override
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
public String testNested() {

    Book book = new Book();
    book.setName("English");
    book.setCreateTime(LocalDateTime.now());
    bookService.save(book);

    // 这里一定要获取到代理对象来调用 orderSave,不然orderSave 的事务不生效
    UserOrderService userOrderService = (UserOrderService) AopContext.currentProxy();
    try {
        userOrderService.orderSave();
    } catch (Exception e) {
        System.out.println(e.getMessage());
    }


    return "success";
}


@Override
@Transactional(rollbackFor = Exception.class, propagation = Propagation.NESTED)
public String orderSave() {
    // case7(当前方法propagation = Propagation.NESTED):
    //      外部方法事务回滚,会导致内部事务一起回滚
    //      内部方法回滚,不会造成外部事务回滚

    UserOrder userOrder = new UserOrder();
    userOrder.setUser("user1");
    userOrder.setAmount(100);

    save(userOrder);
    // 模拟错误-回滚
    int a = 0;
    int b = 10 / a;
    return "success";
}

/*
   对一外部方法的加在 内部方法调用上的 try catch,
   这里一定要站在 内外部方法是否是同一个事务中来看待这些传播行为:

     REQUIRED :即使吃掉异常,也会回滚,因为是同一个事务
     NESTED :吃掉异常,外部方法不会回滚,只会滚 内部 方法,因为有存档点
     REQUIRES_NEW :吃掉异常,外部方法不会回滚,只会滚 内部 方法,因为是不同事务
     NEVER :吃掉异常,外部方法不会回滚,内部方法报错不执行而已
 */
    try {
        userOrderService.orderSave();
    } catch (Exception e) {
        System.out.println(e.getMessage());
    }