Spring @Transactional事务传播机制详解

本文详细介绍了Spring的7种事务传播行为:REQUIRED、SUPPORTS、MANDATORY、REQUIRED_NEW、NOT_SUPPORTED、NEVER和NESTED。通过实例解析了它们在事务管理中的作用,帮助读者理解何时使用不同的传播级别,以便更精确地控制事务边界,避免事务粒度过大导致的问题。

我们日常工作中极少使用事务传播级别,单纯只是使用事务和rollbackfor抛出异常来解决事务问题,但其实我们很多时候使用的是不正确的,或者说会造成事务粒度过大,本文详解一下事务传播级别,也让自己更好地处理事务问题。

1. Spring事务传播机制

1.什么是事务传播机制?

举个栗子,方法A是一个事务的方法,方法A执行过程中调用了方法B,那么方法B有无事务以及方法B对事务的要求不同都会对方法A的事务具体执行造成影响,同时方法A的事务对方法B的事务执行也有影响,这种影响具体是什么就由两个方法所定义的事务传播类型所决定。

简单说就是,我们方法调用通常是,一个方法调用另外一个,而不同方法可以有不同的事务,所以传播机制就是指在多个方法,事务要如何传播。

2.Spring事务传播类型Propagation介绍

一共有七种传播类型

  1. Propagation.REQUIRED
  2. Propagation.SUPPORTS
  3. Propagation.MANDATORY
  4. Propagation.REQUIRED_NEW
  5. Propagation.NOT_SUPPORTED
  6. Propagation.NESTED
  7. Propagation.NEVER

本文从案例结合解释一下不同传播类型下多个@Transactional方法会发生什么?在遇到异常情况下,不同传播机制会产生什么影响。

1. Propagation.REQUIRED

这是默认的传播机制,我们最常用的一种,也是@Transactional默认的一种

如果当前没有事务,则自己新建一个事务,如果当前存在事务,则加入这个事务

// 示例1:
@Transactional(propagation = Propagation.REQUIRED)
public void main(){
    insertA();  // 插入A 
    service.sub();   // 调用其他方法
}
// 两个Service中调用,如果同一个要注意不能用this调用,事务不会起作用
@Transactional(propagation = Propagation.REQUIRED)
public void sub(){
    insertB();  //插入B
    throw RuntimeException;     //发生异常抛出
    insertC();  //调用C

简单来说就是,开启一个事务,上面的案例就是当main方法如果没开启事务,那么sub方法就会开启,如果main方法已经@Transactional开启了事务,sub方法就会加入外层方法的事务,所以上面方法执行在遇到异常时候会全部回滚

结果:
A、B、C全部无法插入。

// 示例2:
public void main(){
    insertA();  // 插入A 
    service.sub();   // 调用其他方法
}
// 两个Service中调用,如果同一个要注意不能用this调用,事务不会起作用
@Transactional(propagation = Propagation.REQUIRED)
public void sub(){
    insertB();  //插入B
    throw RuntimeException;     //发生异常抛出
    insertC();  //调用C

结果:
A插入成功,BC开启新的事务,遇到异常回滚,B、C无法插入

2. Propagation.SUPPORTS

当前存在事务,则加入当前事务,如果当前没有事务,就以非事务方法执行

// 示例3:
public void main(){
    insertA();  // 插入A 
    service.sub();   // 调用其他方法
}
// 两个Service中调用,如果同一个要注意不能用this调用,事务不会起作用
@Transactional(propagation = Propagation.SUPPORTS)
public void sub(){
    insertB();  //插入B
    throw RuntimeException;     //发生异常抛出
    insertC();  //调用C

这个和REQUIRED很像,但是里层的sub方法事务取决于main方法,如果main方法有开启那么里面的就和外层事务一起,如果发生异常全部回滚。
结果:
A、B插入成功,C无法插入因为发生异常

3. Propagation.MANDATORY

当前存在事务,则加入当前事务,如果当前事务不存在,则抛出异常。

// 示例4:
public void main(){
    insertA();  // 插入A 
    service.sub();   // 调用其他方法
}
// 两个Service中调用,如果同一个要注意不能用this调用,事务不会起作用
@Transactional(propagation = Propagation.MANDATORY)
public void sub(){
    insertB();  //插入B
    throw RuntimeException;     //发生异常抛出
    insertC();  //调用C

这种情形的执行结果就是insertA存储成功,而insertB和insertC没有存储。b和c没有存储,并不是事务回滚的原因,而是因为main方法没有声明事务,在去执行sub方法时就直接抛出事务要求的异常(如果当前事务不存在,则抛出异常),所以sub方法里的内容就完全没有执行。

结果:
A插入成功,B、C无法插入,方法抛出异常

那么当main方法有事务的情况下

// 示例5:
@Transactional(propagation = Propagation.REQUIRED)
public void main(){
    insertA();  // 插入A 
    service.sub();   // 调用其他方法
}
// 两个Service中调用,如果同一个要注意不能用this调用,事务不会起作用
@Transactional(propagation = Propagation.MANDATORY)
public void sub(){
    insertB();  //插入B
    throw RuntimeException;     //发生异常抛出
    insertC();  //调用C

结果:
A、B、C全部无法插入,A、B回滚

4. Propagation.REQUIRED_NEW

创建一个新事务,如果存在当前事务,则挂起该事务。

// 示例5:
@Transactional(propagation = Propagation.REQUIRED)
public void main(){
    insertA();  // 插入A 
    service.sub();   // 调用其他方法
    throw RuntimeException;     //发生异常抛出
}
// 两个Service中调用,如果同一个要注意不能用this调用,事务不会起作用
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void sub(){
    insertB();  //插入B
    insertC();  //调用C

因为sub方法会开启一个新的事务,所以main方法抛出的异常并不会影响sub方法的提交
结果:
A插入失败,B、C能插入成功

5. Propagation.NOT_SUPPORTED

始终以非事务方式执行,如果当前存在事务,则挂起当前事务

// 示例6:
@Transactional(propagation = Propagation.REQUIRED)
public void main(){
    insertA();  // 插入A 
    service.sub();   // 调用其他方法
}
// 两个Service中调用,如果同一个要注意不能用this调用,事务不会起作用
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void sub(){
    insertB();  //插入B
    throw RuntimeException;     //发生异常抛出
    insertC();  //调用C

示例6因为当main方法有事务的时候,就会挂起当前事务
即main以事务运行,sub不以事务运行
所以最终结果:
A因为sub抛出异常事务回滚,插入失败,B因为不以事务运行插入成功,C因为遇到异常,后续不会执行,所以插入失败。

// 示例7:
public void main(){
    insertA();  // 插入A 
    service.sub();   // 调用其他方法
}
// 两个Service中调用,如果同一个要注意不能用this调用,事务不会起作用
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void sub(){
    insertB();  //插入B
    throw RuntimeException;     //发生异常抛出
    insertC();  //调用C

示例7这种情况就是所有方法都不会以事务运行,
A、B均能插入成功,C无法插入

6. Propagation.NEVER

不使用事务,如果当前事务存在,则抛出异常

// 示例7:
@Transactional(propagation = Propagation.REQUIRED)
public void main(){
    insertA();  // 插入A 
    service.sub();   // 调用其他方法
}
// 两个Service中调用,如果同一个要注意不能用this调用,事务不会起作用
@Transactional(propagation = Propagation.NEVER)
public void sub(){
    insertB();  //插入B
    insertC();  //调用C

sub因为是Never所以是不会执行直接抛出错误,所以main的事务遇到异常直接回滚,
所以A回滚无法插入,B、C不会插入。

7. Propagation.NESTED

如果当前事务存在,则在嵌套(父子)事务中执行,否则REQUIRED的操作一样(开启一个事务)

// 示例7:
@Transactional(propagation = Propagation.REQUIRED)
public void main(){
    insertA();  // 插入A 
    service.sub();   // 调用其他方法
    throw RuntimeException;     //发生异常抛出
}
// 两个Service中调用,如果同一个要注意不能用this调用,事务不会起作用
@Transactional(propagation = Propagation.NESTED)
public void sub(){
    insertB();  //插入B
    insertC();  //调用C

这个是最需要理解的一种传播机制,要理清楚嵌套(父子)事务,main的是父事务,sub是子事务,main发生异常全部都会回滚。
结果:
A、B、C全部回滚

// 示例8:
@Transactional(propagation = Propagation.REQUIRED)
public void main(){
    insertA();  // 插入A 
    try {
   		 service.sub();   // 调用其他方法
	} catch (Exception e) {
		
	}
	insertD();
}
// 两个Service中调用,如果同一个要注意不能用this调用,事务不会起作用
@Transactional(propagation = Propagation.NESTED)
public void sub(){
    insertB();  //插入B
    throw RuntimeException;     //发生异常抛出
    insertC();  //调用C

示例8,子事务发生异常抛出,但父事务catch掉了,那么这个时候main方法就相当于正常执行没有发生异常,那么就只有子事务回滚。
结果:
A、D插入成功,B、C插入失败

  • REQUIRED
    内外同一个事务,任何一个地方抛出异常全部一起回滚。

  • REQUIRED_NEW
    内部开启一个新的事务,外部事务回滚并不会影响内部的事务,而如果内部事务抛出被catch也不会影响外部事务。

怎么样快速记忆,七个分四组,221这样记,两个一对互相类似

传播类型含义
group1Propagation.REQUIRED如果当前已有事务则加入当前事务,否则开启新的事务
group1Propagation.REQUIRED_NEW无论当前是否有事务都开启新的事务
group2Propagation.SUPPORTED如果当前事务存在就加入事务,否则以非事务运行
group2Propagation.NOT_SUPPORTED始终以非事务方式执行,如果当前存在事务,则挂起当前事务
group3Propagation.NEVER不使用事务,如果当前事务存在,则抛出异常
group3Propagation.MANDATORY当前存在事务,则加入当前事务,如果当前事务不存在,则抛出异常。
group4Propagation.NESTED父子(嵌套)事务,父回滚全回滚,子回滚不影响父事务

2.具体案例

单纯讲案例比较枯燥,会觉得工作中什么情况会使用到呢,这边就举一个例子来讲解一下。

在下单时候,我们最主要是写入订单、然后添加积分,最后记录日志

 @Service
   public class OrderServiceImpl implements OrderService{
        
        @Transactional
        public void placeOrder(OrderDTO orderDTO){
                   
            try {
                pointService.addPoint(Point point);
            } catch (Exception e) {
               // 记录错误信息
            }
            //省略...
        }
        //省略...
   }
   @Service
   public class PointServiceImpl implements PointService{
        
        @Transactional(propagation = Propagation.NESTED)
        public void addPoint(Point point){
                   
            try {
                recordService.addRecord(Record record);
            } catch (Exception e) {
               //省略...
            }
            //省略...
        }
        //省略...
   }
   @Service
   public class RecordServiceImpl implements RecordService{
        
        @Transactional(propagation = Propagation.NOT_SUPPORTED)
        public void addRecord(Record record){
                   
           
            //省略...
        }
        //省略...
   }

下单的操作不会影响添加积分的操作,所以我们使用NESTED,下单只要成功,添加积分可以成功或失败,失败的话就错误信息后续补偿。
而记录日志我们可以有也可以没有,就可以设置为NOT_SUPPORTED不开启事务,使得事务的方法能尽可能的精简,避免一个很大的事务方法。

总结

本文讲解了Spring事务的七种传播机制,我们可以根据具体的类型,具体设置,避免事务的方法过于长,一个事务里面调用的库表越多,就越有可能造成死锁,所以我们要根据具体的需要拆分使用。

<think>我们正在讨论Spring框架中的@Transactional注解的事务传播机制事务传播机制定义了在多个事务方法相互调用时,事务应该如何传播Spring提供了7种传播行为,定义在Propagation枚举中。 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_REQUIRED ```java @Service public class UserService { @Autowired private UserRepository userRepository; @Transactional(propagation = Propagation.REQUIRED) public void createUser(User user) { userRepository.save(user); } } ``` 如果调用`createUser`方法时已经存在事务,则该方法会加入该事务;如果没有事务,则创建一个新的事务。 ### 示例2:PROPAGATION_REQUIRES_NEW ```java @Service public class AccountService { @Autowired private AccountRepository accountRepository; @Transactional(propagation = Propagation.REQUIRES_NEW) public void updateAccount(Account account) { accountRepository.save(account); } } ``` 无论当前是否存在事务,`updateAccount`方法都会创建一个新的事务。如果当前存在事务,则将该事务挂起。 ### 示例3:PROPAGATION_NESTED ```java @Service public class OrderService { @Autowired private OrderRepository orderRepository; @Transactional(propagation = Propagation.NESTED) public void createOrder(Order order) { orderRepository.save(order); } } ``` 如果当前存在事务,则`createOrder`方法会创建一个嵌套事务。嵌套事务是外部事务的子事务,在外部事务回滚时,嵌套事务也会回滚;而嵌套事务可以独立于外部事务进行回滚(通过保存点实现)。 ### 注意事项: - 事务传播行为是在方法调用时确定的。 - 使用`PROPAGATION_REQUIRES_NEW`时,会创建新的事务,同时挂起当前事务(如果存在),这意味着新事务和原事务是独立的,新事务的回滚不会影响原事务,但原事务的回滚会影响新事务(因为新事务已经提交)吗?注意:新事务已经独立提交,原事务回滚不会影响新事务,因为新事务已经提交了。但是,如果新事务抛出了异常,且没有被捕获,则会导致原事务也被标记为回滚(除非原事务捕获了异常并处理)。 - `PROPAGATION_NESTED`使用保存点(Savepoint)来实现,因此只适用于具有保存点功能的事务管理器(如JDBC的DataSourceTransactionManager),而不适用于JTA(全局事务)。 ### 为什么需要传播机制? 在复杂的业务场景中,多个事务方法相互调用,需要明确事务的边界和传播规则,以保证数据的一致性和完整性。不同的传播行为提供了灵活的事务控制方式。 ### 总结: 事务传播机制Spring事务管理中的重要概念,通过合理设置传播行为,可以有效地控制事务的边界和多个事务之间的影响。 §§相关问题§§: 1. Spring事务的隔离级别有哪些? 2. 在Spring中,@Transactional注解在什么情况下会失效? 3. 如何配置Spring事务的超时时间? 4. 事务的嵌套和挂起分别是什么意思? </think>### Java @Transactional事务传播机制Spring框架中,`@Transactional`的事务传播机制定义了**多个事务方法相互调用时**事务如何传递。Spring提供了7种传播行为(定义在`Propagation`枚举中): ```java public enum Propagation { REQUIRED, // 默认值:存在事务则加入,否则新建 SUPPORTS, // 存在事务则加入,否则非事务运行 MANDATORY, // 存在事务则加入,否则抛异常 REQUIRES_NEW, // 新建事务,挂起当前事务(存在时) NOT_SUPPORTED, // 非事务运行,挂起当前事务(存在时) NEVER, // 非事务运行,存在事务则抛异常 NESTED // 嵌套事务(基于保存点) } ``` #### 核心机制详解(附代码示例) ##### 1. **REQUIRED(默认)** - **行为**:当前存在事务则加入,否则创建新事务 - **场景**:嵌套调用需原子性操作 - **代码**: ```java @Transactional(propagation = Propagation.REQUIRED) public void methodA() { // 事务T1 methodB(); // 加入T1 } @Transactional(propagation = Propagation.REQUIRED) public void methodB() { // 加入已有事务T1 } ``` ##### 2. **REQUIRES_NEW** - **行为**:始终创建新事务,挂起当前事务(若有) - **场景**:独立操作需提交(如日志记录) - **代码**: ```java @Transactional(propagation = Propagation.REQUIRED) public void orderProcess() { // 事务T1 logService.auditLog(); // 新启事务T2 // T1异常不影响T2提交 } @Service public class LogService { @Transactional(propagation = Propagation.REQUIRES_NEW) public void auditLog() { // 独立事务T2 } } ``` ##### 3. **NESTED** - **行为**:在已有事务中创建嵌套事务(基于保存点) - **特性**:外层事务回滚时嵌套事务必回滚,嵌套事务可单独回滚 - **代码**: ```java @Transactional public void purchase() { try { inventoryService.deductStock(); // 嵌套事务 } catch (Exception e) { // 仅回滚库存操作 } // 主事务继续 } @Service public class InventoryService { @Transactional(propagation = Propagation.NESTED) public void deductStock() { // 嵌套事务(Savepoint) } } ``` ##### 4. **MANDATORY** - **行为**:强制要求存在事务,否则抛`IllegalTransactionStateException` - **场景**:确保方法在事务中被调用 ```java @Transactional(propagation = Propagation.MANDATORY) public void refund() { // 必须在事务中调用 } ``` #### ⚠️ 关键注意事项 1. **事务挂起**:`REQUIRES_NEW`/`NOT_SUPPORTED`会挂起原事务,使用独立的数据库连接 2. **嵌套事务限制**:`NESTED`需要JDBC 3.0+驱动(如MySQL的InnoDB引擎) 3. **生效条件**:调用必须通过代理对象(同类内调用失效,需使用`AopContext.currentProxy()`) #### 传播行为选择建议 | 传播行为 | 适用场景 | |----------------|----------------------------------| | REQUIRED | 大多数业务逻辑(默认选择) | | REQUIRES_NEW | 独立操作(审计日志、消息发送) | | NESTED | 可部分回滚的子操作(库存扣减) | | MANDATORY | 强制事务上下文(支付核心操作) |
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值