Spring事务嵌套机制

本文回顾了Spring事务机制,介绍了事务传播行为的7种类型。指出在同一个类中,方法调用有注解的方法时注解可能无效,原因是未通过代理类调用。还给出解决方法,如将方法分开到不同类、把注解加到类名上,最后得出Service内部嵌套调用一般不开启新事务的结论。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Spring 事务机制回顾

   Spring事务一个被讹传很广说法是:一个事务方法不应该调用另一个事务方法,否则将产生两个事务.  结果造成开发人员在设计事务方法时束手束脚,生怕一不小心就踩到地雷。 
  其实这是不认识Spring事务传播机制而造成的误解,Spring对事务控制的支持统一在TransactionDefinition类中描述,该类有以下   几个重要的接口方法:

  1. int getPropagationBehavior():事务的传播行为
  2. int getIsolationLevel():事务的隔离级别
  3. int getTimeout():事务的过期时间
  4. boolean isReadOnly():事务的读写特性
  5. 除了事务的传播行为外,事务的其他特性Spring是借助底层资源的功能来完成的,Spring无非只充当个代理的角色。但是事务的传播行为却是Spring凭借自身的框架提供的功能 ;

Spring事务传播属性:

   所谓事务传播行为就是多个事务方法相互调用时,事务如何在这些方法间传播。

Spring支持以下7种事务传播行为:

  1. propagation-required: 支持当前事务,如果有就加入当前事务中;如果当前方法没有事务,就新建一个事务;
  2. propagation-supports: 支持当前事务,如果有就加入当前事务中;如果当前方法没有事务,就以非事务的方式执行;
  3. propagation-mandatory: 支持当前事务,如果有就加入当前事务中;如果当前没有事务,就抛出异常;
  4. propagation-requires_new: 新建事务,如果当前存在事务,就把当前事务挂起;如果当前方法没有事务,就新建事务;
  5. propagation-not-supported: 以非事务方式执行,如果当前方法存在事务就挂起当前事务;如果当前方法不存在事务,就以非事务方式执行;
  6. propagation-never: 以非事务方式执行,如果当前方法存在事务就抛出异常;如果当前方法不存在事务,就以非事务方式执行;
  7. propagation-nested: 如果当前方法有事务,则在嵌套事务内执行;如果当前方法没有事务,则与required操作类似;


前六个策略类似于EJB CMT,第七个(PROPAGATION_NESTED)是Spring所提供的一个特殊变量。
它要求事务管理器或者使用JDBC 3.0 Savepoint API提供嵌套事务行为(如Spring的DataSourceTransactionManager)
在同一个类中,一个方法调用另外一个有注解(比如@Async,@Transational)的方法,注解是不会生效的

代码示例:

 例子中,有两方法,一个有@Transational注解,一个没有。如果调用了有注解的addPerson()方法,会启动一个Transaction;如果调用updatePersonByPhoneNo(),因为它内部调用了有注解的addPerson(),如果你以为系统也会为它启动一个Transaction,那就错了,实际上是没有的 

@Service
public class PersonServiceImpl implements PersonService {
 
 @Autowired
 PersonDao personDao;
 
 @Override
 @Transactional
 public boolean addPerson(Person person) {
  boolean result = personDao.insertPerson(person)>0 ? true : false;
  return result;
 }
 
 @Override
 //@Transactional
 public boolean updatePersonByPhoneNo(Person person) {
  boolean result = personDao.updatePersonByPhoneNo(person)>0 ? true : false;
  addPerson(person); //测试同一个类中@Transactional是否起作用
  return result;
 }
}
原因:

spring 在扫描bean的时候会扫描方法上是否包含@Transactional注解,如果包含,spring会为这个bean动态地生成一个子类(即代理类,proxy),代理类是继承原来那个bean的。此时,当这个有注解的方法被调用的时候,实际上是由代理类来调用的,代理类在调用之前就会启动transaction。然而,如果这个有注解的方法是被同一个类中的其他方法调用的,那么该方法的调用并没有通过代理类,而是直接通过原来的那个bean,所以就不会启动transaction,我们看到的现象就是@Transactional注解无效。

 

为什么一个方法a()调用同一个类中另外一个方法b()的时候,b()不是通过代理类来调用的呢?可以看下面的例子(为了简化,用伪代码表示):

@Service
class A{
    @Transactinal
    method b(){...}
    
    method a(){    //标记1
        b();
    }
}
 
//Spring扫描注解后,创建了另外一个代理类,并为有注解的方法插入一个startTransaction()方法:
class proxy$A{
    A objectA = new A();
    method b(){    //标记2
        startTransaction();
        objectA.b();
    }
 
    method a(){    //标记3
        objectA.a();    //由于a()没有注解,所以不会启动transaction,而是直接调用A的实例的a()方法
    }
}
当我们调用A的bean的a()方法的时候,也是被proxy$A拦截,执行proxy$A.a()(标记3),然而,由以上代码可知,这时候它调用的是objectA.a(),也就是由原来的bean来调用a()方法了,所以代码跑到了“标记1”。由此可见,“标记2”并没有被执行到,所以startTransaction()方法也没有运行。

 

解决的方法就简单了(两种):

把这两个方法分开到不同的类中;
把注解加到类名上面;
结论:

在一个Service内部,事务方法之间的嵌套调用,普通方法和事务方法之间的嵌套调用,都不会开启新的事务.

1. spring采用动态代理机制来实现事务控制,而动态代理最终都是要调用原始对象的,而原始对象在去调用方法时,是不会再触发代理了!

2. Spring的事务管理是通过AOP实现的,其AOP的实现对于非final类是通过cglib这种方式,即生成当前类的一个子类作为代理类,然后在调用其下的方法时,会判断这个方法有没有@Transactional注解,如果有的话,则通过动态代理实现事务管理(拦截方法调用,执行事务等切面)。当b()中调用a()时,发现b()上并没有@Transactional注解,所以整个AOP代理过程(事务管理)不会发生。

<think>嗯,用户想了解Spring事务嵌套,我需要详细解释清楚。首先,我应该从基本概念入手,介绍事务嵌套是什么。然后,可能需要讲传播机制,特别是PROPAGATION_REQUIRED和PROPAGATION_REQUIRES_NEW的区别。记得要举例子,比如方法A调用方法B,不同传播行为下的表现。 用户可能遇到事务嵌套的问题,比如内层事务回滚是否影响外层,或者如何配置不同的传播行为。需要说明默认的传播行为是什么,以及常见的使用场景。比如,当方法B需要独立事务时,使用REQUIRES_NEW,这样B的回滚不会影响A,但要注意异常处理。 另外,要提到事务的隔离级别和它们对嵌套事务的影响,但可能不需要太深入,除非用户特别问到。需要强调事务失效的常见情况,比如自调用问题,以及如何通过AOP代理解决。 还要注意语法部分,确保数学表达式和代码都用正确的格式。比如传播行为应该用代码块标出,例如@Transactional(propagation = Propagation.REQUIRES_NEW)。例子部分要清晰,可能用代码示例说明不同传播行为下的结果。 最后,总结一下关键点,帮助用户记忆。可能还需要提醒常见的陷阱,比如异常被捕获导致事务不回滚,或者配置错误导致传播行为不生效。确保回答结构清晰,分点说明,让用户容易理解。</think>Spring事务嵌套是分布式事务处理中的典型场景,其核心通过传播机制(Propagation Behavior)控制事务边界。以下从工作机制到实际场景逐步分析: 一、传播机制7种类型(关键4种) $$ \begin{array}{|l|l|} \hline \text{传播行为} & \text{行为特征} \\ \hline \text{PROPAGATION\_REQUIRED(默认)} & \text{加入当前事务,不存在则新建} \\ \hline \text{PROPAGATION\_REQUIRES\_NEW} & \text{始终新建独立事务} \\ \hline \text{PROPAGATION\_NESTED} & \text{嵌套事务(Savepoint机制)} \\ \hline \text{PROPAGATION\_SUPPORTS} & \text{跟随当前事务状态} \\ \hline \end{array} $$ 二、典型嵌套场景分析(A方法调用B方法) 1. **默认传播行为(REQUIRED)** ```java @Transactional void A() { insertTable1(); B(); // 传播行为=REQUIRED // 异常发生点影响最终结果 } @Transactional void B() { insertTable2(); } ``` - 若B()执行失败:A()中insertTable1和B()的insertTable2同时回滚 - 若B()成功,A()后续失败:所有操作回滚 2. **独立事务(REQUIRES_NEW)** ```java @Transactional void A() { insertTable1(); B(); // 传播行为=REQUIRES_NEW throw new Exception(); } @Transactional(propagation = Propagation.REQUIRES_NEW) void B() { insertTable2(); } ``` - B()的事务先提交 - A()抛出异常后,仅回滚insertTable1 3. **嵌套事务(NESTED)** ```java @Transactional void A() { insertTable1(); try { B(); // 传播行为=NESTED } catch (Exception e) { // 处理子事务异常 } insertTable3(); } @Transactional(propagation = Propagation.NESTED) void B() { insertTable2(); throw new RuntimeException(); } ``` - B()回滚到Savepoint,不影响A()后续insertTable3执行 - 需要数据库支持(如MySQL的InnoDB引擎) 三、实现原理 1. 通过`TransactionInterceptor`实现AOP切面 2. 使用`TransactionSynchronizationManager`管理事务上下文 3. 事务管理器(`PlatformTransactionManager`)决策传播行为 四、实践注意事项 1. **异常处理**:默认只回滚RuntimeException ```java @Transactional(rollbackFor = Exception.class) ``` 2. **事务失效场景**: - 自调用(同类中非代理方法调用) - 异常被捕获未抛出 - 非public方法 五、调试建议 1. 开启事务日志: ```properties logging.level.org.springframework.transaction.interceptor=TRACE logging.level.org.springframework.jdbc.datasource=DEBUG ``` 2. 使用`TransactionSynchronizationManager.isActualTransactionActive()`检测事务状态 理解事务嵌套的关键是掌握传播行为对事务边界的影响,实际开发中需要根据业务原子性要求选择合适的传播策略。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值