spring service层方法调用同类中的方法 事务不生效?

本文深入探讨了Spring框架中事务管理的机制,特别是在不同场景下(如跨类调用和服务层内部方法调用)事务的有效性。揭示了@Transactional注解在类内部方法调用时为何失效,以及如何通过配置和使用代理调用来解决这一问题。
有这样的一道面试题:在service层调用别的service层的方法,他们的事务能否生效;如果是在同一个类中调用带有@Transactional注解的方法,此时,他们的事务能否生效?
看了许多大神的blog,今天来做一下总结:

先给出大家答案:

  • 不同类之间的方法调用,如类A的方法a()调用类B的方法b(),这种情况事务是正常起作用的。只要方法a()或b()配置了事务,运行中就会开启事务,产生代理。
  • 同一类内方法调用,无论被调用的b()方法是否配置了事务,此事务在被调用时都将不生效。
  1. 首先先说一下Spring事务管理详解:下面的这篇博客介绍的很清楚了,从基本原理、事务的特性、隔离级别以及事务实现的三种方式
    Spring事务管理详解
  2. 知道了事务的一些知识后,下面说一下@Transactional注解的信息(大家着重看一下4 5 6条的解释)
1.在需要事务管理的地方加@Transactional 注解。@Transactional 注解可以被应用于接口定义和接口方法、类定义和类的 public 方法上。

2.@Transactional 注解只能应用到 public 可见度的方法上。 如果你在 protected、private 或者 package-visible 的方法上使用 @Transactional 注解,它也不会报错, 但是这个被注解的方法将不会展示已配置的事务设置。

3.注意仅仅 @Transactional 注解的出现不足于开启事务行为,它仅仅 是一种元数据。必须在配置文件中使用配置元素,才真正开启了事务行为。(spring配置文件中,开启声明式事务)

4.通过 元素的 “proxy-target-class” 属性值来控制是基于接口的还是基于类的代理被创建。如果 “proxy-target-class” 属值被设置为 “true”,那么基于类的代理将起作用(这时需要CGLIB库cglib.jar在CLASSPATH中)。如果 “proxy-target-class” 属值被设置为 “false” 或者这个属性被省略,那么标准的JDK基于接口的代理将起作用。

5.Spring团队建议在具体的类(或类的方法)上使用 @Transactional 注解,而不要使用在类所要实现的任何接口上。在接口上使用 @Transactional 注解,只能当你设置了基于接口的代理时它才生效。因为注解是 不能继承 的,这就意味着如果正在使用基于类的代理时,那么事务的设置将不能被基于类的代理所识别,而且对象也将不会被事务代理所包装。

6.@Transactional的事务开启 ,或者是基于接口的 或者是基于类的代理被创建。所以在同一个类中一个无事务的方法调用另一个有事务的方法,事务是不会起作用的。
  1. 不生效的原因:

       当从类外调用方法a()时,从spring容器获取到的serviceImpl对象实际是包装好的proxy对象,因此调用a()方法的对象是动态代理对象。而在类内部a()调用b()的过程中,实质执行的代码是this.b(),此处this对象是实际的serviceImpl对象而不是本该生成的代理对象,因此直接调用了b()方法。

  1. 解决办法:
  1. 放到不同的类中进行调用
  2. 在spring配置文件中加入配置
    <aop:aspectj-autoproxy/>
    <aop:aspectj-autoproxy proxy-target-class=“true” expose-proxy=“true” />
  3. 将之前使用普通调用的方法,换成使用代理调用
    ((TestService)AopContext.currentProxy()).testTransactional2();
    获取到TestService的代理类,再调用事务方法,强行经过代理类,激活事务切面。
  4. 使用异步操作,另外开启一个线程或者将这个消息写入到队列里面,在其他的地方进行处理

感谢我调用链接的几位大神:

关于加@Transactional注解的方法之间调用,事务是否生效的问题
spring的service类调用自己方法事务无效
Spring事务不起作用 问题汇总

<think>我们讨论的是在同一个类中,一个事务方法调用另一个事务方法的情况。根据Spring事务管理机制,这种内部调用会导致事务失效,原因与之前类似:Spring事务管理是通过代理实现的,当在同一个类中直接调用时,会绕过代理,因此事务注解不会被代理处理。 但是,如果调用发生在不同类之间,那么事务是正常生效的。所以,问题中的事务方法调用事务方法”需要区分两种情况: 1. 同一个类中,事务方法A调用事务方法B(内部调用) -> 事务B不会生效(因为绕过了代理) 2. 不同类中,事务方法A调用事务方法B -> 事务B会生效(通过代理调用) 另外,即使是在同一个类中,如果通过代理对象来调用内部的事务方法,那么事务也会生效(如之前提到的自注入或AopContext方式)。 下面我们通过代码示例来说明两种情况: 情况1:同一个类中内部调用事务方法失效) ```java @Service public class OrderService { public void placeOrder(Order order) { // 直接内部调用事务不会生效 processPayment(order); } @Transactional public void processPayment(Order order) { // 数据库操作 } } ``` 情况2:不同类调用事务方法(生效) ```java @Service public class OrderService { @Autowired private PaymentService paymentService; public void placeOrder(Order order) { paymentService.processPayment(order); // 通过代理调用事务生效 } } @Service public class PaymentService { @Transactional public void processPayment(Order order) { // 数据库操作 } } ``` 那么,如果我们在同一个类中希望内部事务方法调用生效,可以采用之前提到的解决方案,例如自注入: 解决方案:使用自注入(在同一个类中通过代理对象调用) ```java @Service public class OrderService { @Autowired private OrderService self; // 注入自身代理 public void placeOrder(Order order) { // 通过代理对象调用事务方法 self.processPayment(order); } @Transactional public void processPayment(Order order) { // 数据库操作 } } ``` 注意:使用自注入需要确保Spring的代理机制能够处理循环依赖(默认是允许的,但如果有构造器注入则可能失败)。 此外,我们还需要注意事务的传播行为。如果两个事务方法同一事务上下文中被调用,那么它们可能会在同一事务中运行(取决于传播行为的设置)。例如,如果外部方法没有事务,内部方法使用`@Transactional(propagation = Propagation.REQUIRED)`,则内部方法会启动一个新的事务。但是,如果外部方法事务,则内部方法会加入外部方法事务(默认传播行为是REQUIRED)。 因此,在讨论事务方法调用事务方法是否生效时,我们不仅要考虑调用方式(是否通过代理),还要考虑传播行为的设置。 总结: 1. 同一个类中直接内部调用事务方法事务不会生效。 2. 跨类调用事务方法事务生效。 3. 同一个类中通过代理对象(自注入或AopContext)调用事务方法事务生效。 下面我们通过一个例子展示传播行为的影响: 假设我们有一个服务类,其中有两个事务方法,并且通过代理对象调用: ```java @Service public class AccountService { @Autowired private AccountService self; // 自注入代理 @Transactional public void transfer(Account from, Account to, double amount) { // 先扣款 self.debit(from, amount); // 再存款 self.credit(to, amount); } @Transactional(propagation = Propagation.REQUIRED) public void debit(Account account, double amount) { // 扣款操作 } @Transactional(propagation = Propagation.REQUIRES_NEW) public void credit(Account account, double amount) { // 存款操作 } } ``` 在这个例子中: - `debit`方法使用默认的REQUIRED传播行为,因此它会加入到`transfer`方法事务中。 - `credit`方法使用REQUIRES_NEW传播行为,因此无论`transfer`方法是否有事务,它都会启动一个新的事务,并且会挂起`transfer`方法事务(如果存在)。 注意:由于我们通过`self`代理对象调用,所以`debit`和`credit`方法事务都会生效。 但是,如果没有使用代理对象(直接内部调用),那么这两个方法事务都不会生效(因为注解被绕过)。 因此,在编写事务方法时,务必注意调用方式。 最后,我们给出一个使用`TransactionTemplate`编程式事务管理的例子,这样可以在同一个类中灵活控制事务: ```java @Service public class AccountService { @Autowired private TransactionTemplate transactionTemplate; public void transfer(Account from, Account to, double amount) { // 外部事务 transactionTemplate.execute(status -> { debit(from, amount); // 内嵌事务(注意:这里不能直接使用this.credit,因为需要事务控制) transactionTemplate.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) { credit(to, amount); } }); return null; }); } // 注意:这些方法不需要@Transactional注解 private void debit(Account account, double amount) { // 扣款操作 } private void credit(Account account, double amount) { // 存款操作 } } ``` 在编程式事务中,我们可以嵌套多个事务,并且可以精确控制每个事务的边界。 但是,请注意,`TransactionTemplate`的嵌套使用需要理解事务传播机制(默认是PROPAGATION_REQUIRED,所以嵌套事务会加入到当前事务,除非设置传播行为为REQUIRES_NEW)。 所以,为了在同一个类中实现事务方法的嵌套调用,编程式事务是一种可行的方式。 综上所述,事务方法调用事务方法是否生效,取决于调用方式(是否通过代理)以及事务传播行为的设置。 §§相关问题§§: 1. Spring 事务的传播行为有哪些?请详细说明每种行为。 2. 在同一事务方法中,如何捕获异常但不回滚事务? 3. 如何配置 Spring 事务的超时时间? 4. 在分布式事务场景下,Spring 事务管理有哪些局限性?</think>### Spring 事务方法调用事务方法的执行机制 在 Spring 中,当一个事务方法调用另一个事务方法时,事务行为是否生效取决于两个关键因素: 1. **调用方式**(是否通过代理对象) 2. **事务传播行为**(`@Transactional(propagation = ...)` 的设置) #### 核心规则分析 | 调用场景 | 传播行为 | 事务生效 | 说明 | |---------|---------|---------|------| | 同类的内部调用 | 任意 | ❌ 不生效 | 绕过代理机制 | | 跨类调用 | REQUIRED | ✅ 生效 | 加入现有事务 | | 跨类调用 | REQUIRES_NEW | ✅ 生效 | 创建新事务 | | 通过代理调用 | 任意 | ✅ 生效 | 正确经过代理 | --- ### 解决方案及代码示例 #### 方案 1:跨类调用(默认传播行为 - REQUIRED) ```java @Service public class OrderService { @Autowired private PaymentService paymentService; // 注入另一个服务 @Transactional public void placeOrder(Order order) { // 跨类调用事务方法 - 加入当前事务 paymentService.processPayment(order); inventoryService.updateStock(order); } } @Service public class PaymentService { @Transactional // 默认传播行为 REQUIRED public void processPayment(Order order) { // 此方法将加入 placeOrder 的事务 accountService.deduct(order.getAmount()); transactionLogService.log(order); } } ``` **执行流程**: 1. `placeOrder` 启动事务 2. `processPayment` 加入同一事务 3. 所有操作在同一事务中提交或回滚 --- #### 方案 2:使用 REQUIRES_NEW 传播行为 ```java @Service public class AuditService { @Transactional(propagation = Propagation.REQUIRES_NEW) public void logOperation(String action) { // 此方法总是创建新事务 auditRepository.save(new AuditLog(action)); } } @Service public class UserService { @Autowired private AuditService auditService; @Transactional public void updateUser(User user) { userRepository.save(user); try { // 跨类调用 - 创建独立事务 auditService.logOperation("USER_UPDATE"); } catch (Exception e) { // 审计日志失败不影响主事务 logger.error("Audit failed", e); } } } ``` **关键特性**: - 独立事务提交/回滚 - 外事务异常不影响内 - 内事务异常可选择是否影响外 --- #### 方案 3:同类方法通过代理调用 ```java @Service public class InventoryService { @Autowired private InventoryService selfProxy; // 自注入代理 public void batchUpdate(List<Item> items) { items.forEach(item -> { // 通过代理调用事务方法 selfProxy.updateSingleItem(item); }); } @Transactional(propagation = Propagation.REQUIRES_NEW) public void updateSingleItem(Item item) { // 每个更新在独立事务中 inventoryRepo.update(item); historyRepo.logUpdate(item); } } ``` **配置要求**: ```java @SpringBootApplication @EnableAspectJAutoProxy(exposeProxy = true) // 关键配置 public class App {} ``` --- ### 事务传播行为详解 Spring 提供 7 种传播行为,最常用的有: 1. **REQUIRED**(默认): - 存在事务则加入,不存在则新建 ```java @Transactional(propagation = Propagation.REQUIRED) ``` 2. **REQUIRES_NEW**: - 总是创建新事务,挂起当前事务(如果存在) ```java @Transactional(propagation = Propagation.REQUIRES_NEW) ``` 3. **NESTED**: - 在现有事务中创建嵌套事务(保存点) ```java @Transactional(propagation = Propagation.NESTED) ``` 4. **SUPPORTS**: - 存在事务则加入,不存在则以非事务方式运行 ```java @Transactional(propagation = Propagation.SUPPORTS) ``` --- ### 事务嵌套执行示例 ```java @Service public class FinancialService { @Transactional public void transfer(Account from, Account to, double amount) { // 操作1:扣款(主事务) accountService.debit(from, amount); // 操作2:存款(独立事务) try { accountService.credit(to, amount); } catch (Exception e) { // 存款失败不影响扣款 refundService.scheduleRefund(from, amount); } } } @Service public class AccountService { @Transactional(propagation = Propagation.REQUIRED) public void debit(Account account, double amount) { // 加入主事务 } @Transactional(propagation = Propagation.REQUIRES_NEW) public void credit(Account account, double amount) { // 独立事务执行 } } ``` **执行结果**: - 扣款操作回滚 ⇒ 整个事务回滚 - 存款操作失败 ⇒ 仅存款操作回滚,扣款仍有效 - 扣款成功 + 存款成功 ⇒ 全部提交 --- ### 最佳实践建议 1. **事务粒度控制**: - 保持事务方法精简 - 避免超过 3 嵌套调用 2. **异常处理原则**: ```java @Transactional public void criticalOperation() { try { // 不会导致回滚的异常 nonCriticalService.process(); } catch (NonCriticalException e) { // 标记为不回滚 TransactionAspectSupport.currentTransactionStatus() .setRollbackOnly(false); } } ``` 3. **性能优化**: - 对只读操作使用 `@Transactional(readOnly = true)` - 避免在循环中调用 REQUIRES_NEW 方法
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值