事务注解@Transactional失效的情况解析

前言

理解@Transactional注解失效的情况,先要谈Spring中注解的实现原理,是以动态代理的形式出现的,如果无法实现动态代理,注解自然就无法生效。另外,如果类没有被Spring管理,其上的@Transactional注解也不会生效。此外,如果执行事务的过程中遇到异常却没有回滚处理,不同事务之间的传播机制的问题,或者Spring管理不正确也会导致失效。

因此,注解@Transactional失效的情况可以分以下几类:

  1. 动态代理无法实现
  2. Spring管理不当
  3. 事务遇到异常未回滚

接下来将分别进行介绍。

1. 动态代理无法实现

首先,搞清楚动态代理的实现方式有两种:

  • 要代理的对象实现了接口:使用JDK Proxy类来创建代理对象。
  • 要代理的对象没有实现接口:使用Cglib生成被代理对象的子类来代理。

而动态代理无法实现的,进一步导致@Transactional注解失效的情况有以下几种:

  1. 方法自调用(也就是递归):通过this引用完成,绕过了代理。

    • 示例:
      @Service
      public class MyService {
          
          @Transactional
          public void methodA() {
              // 业务逻辑
              methodA(); // 自调用,不会应用事务
          }
      }
      
    • 解决方法:methodA() 是被标记为 @Transactional 的方法,事务在调用 recursiveMethod() 时保持生效。
      @Service
      public class MyService {
      
          @Transactional
          public void methodA() {
              // 业务逻辑
              recursiveMethod(0); // 开始递归
          }
      
          private void recursiveMethod(int count) {
              if (count < 5) {
                  // 递归逻辑
                  System.out.println("Count: " + count);
                  recursiveMethod(count + 1); // 继续递归
              }
          }
      }
      
  2. 同一个类中方法之间的调用:

    • 示例:
      @Service
      public class MyService {
          
          @Transactional
          public void outerMethod() {
              innerMethod(); // 事务不会生效
          }
      
          public void innerMethod() {
              // 业务逻辑
          }
      }
      
    • 解决方法:将 innerMethod() 提取到另一个 Spring 管理的 Bean 中,然后通过依赖注入的方式调用它。
      @Service
      public class MyService {
      
          @Autowired
          private MyService myService; // 注入同一个服务
      
          @Transactional
          public void outerMethod() {
              myService.innerMethod(); // 通过代理调用
          }
      
          public void innerMethod() {
              // 业务逻辑
          }
      }
      
  3. 方法的修饰符不是public:由于只有公共的方法才会被代理,如果方法是私有的(private)或保护的(protected),它们不会被 Spring 的代理机制拦截,因此 @Transactional 注解不会生效。

    • 示例:
      @Service
      public class MyService {
      
          // 私有方法,@Transactional 注解将失效
          @Transactional
          private void myPrivateMethod() {
              // 业务逻辑
              System.out.println("Executing private method");
          }
      
          public void execute() {
              myPrivateMethod(); // 自调用,且私有方法不会被代理
          }
      }
      
    • 解决办法:
      @Service
      public class MyService {
      
          // 将方法改为 public,确保事务生效
          @Transactional
          public void myPublicMethod() {
              // 业务逻辑
              System.out.println("Executing public method");
          }
      
          public void execute() {
              myPublicMethod(); // 通过代理调用,事务将生效
          }
      }
      

2. Spring管理不当

  1. 调用对象不是Spring管理的bean,比如new
    • 示例:
      public class MyClass {
          public void myMethod() {
              MyService service = new MyService(); // 直接创建,不是 Spring 管理的
              service.methodA(); // 不会应用事务
          }
      }
      
    • 解决办法:
      @Component
      public class MyClass {
      
          @Autowired
          private MyService myService; // 通过 Spring 注入
      
          public void myMethod() {
              myService.methodA(); // 通过 Spring 管理的对象调用,事务将生效
          }
      }
      

3. 事务遇到异常未回滚

  1. @Transactional 注解默认情况下只会在遇到未检查的异常(如 RuntimeException)时回滚事务。如果抛出的是检查异常(如 IOException),则事务不会回滚。

    • 示例:
      @Transactional
      public void myMethod() throws IOException {
          throw new IOException(); // 不会导致事务回滚
      }
      
    • 解决方法:
      @Transactional(rollbackFor = IOException.class) // 指定检查异常
      public void myMethod() throws IOException {
          throw new IOException(); // 将导致事务回滚
      }
      
  2. required修饰的方法outerMethod()调用required_new修饰的方法innerMethod():在这种情况下,方法innerMethod()的异常不会导致方法outerMethod()的事务回滚,从而导致数据不一致。

    • 示例:
      @Service
      public class OuterService {
      
          @Transactional
          public void outerMethod() {
              // 调用内部方法
              innerMethod(); // 使用 REQUIRES_NEW
          }
      
          @Transactional(propagation = Propagation.REQUIRES_NEW)
          public void innerMethod() {
              // 业务逻辑
              throw new RuntimeException("Exception in innerMethod"); // 仅会回滚 innerMethod 的事务
          }
      }
      
    • 解决办法:
      @Transactional
      public void outerMethod() {
          try {
              innerMethod(); // 处理结果
          } catch (RuntimeException e) {
              // 处理 innerMethod 的异常
              System.out.println("Handled inner exception: " + e.getMessage());
          }
      }
      
  3. required修饰的方法outerMethod()调用nested修饰的方法innerMethod():如果 innerMethod() 抛出异常,innerMethod() 的子事务将回滚,但 outerMethod() 的外部事务仍然可以继续执行。

    • 示例:
      @Service
      public class OuterService {
      
          @Transactional
          public void outerMethod() {
              // 调用内部方法
              innerMethod(); // 使用 NESTED
          }
      
          @Transactional(propagation = Propagation.NESTED)
          public void innerMethod() {
              // 业务逻辑
              throw new RuntimeException("Exception in innerMethod"); // 事务将回滚
          }
      }
      
    • 解决办法:
      @Transactional
      public void outerMethod() {
          try {
              innerMethod(); // 可能会抛出异常
          } catch (RuntimeException e) {
              // 处理异常,可能继续执行其他逻辑
              // 或者决定是否回滚 outerMethod 的事务
              System.out.println("Handled inner exception: " + e.getMessage());
          }
      }
      

以上为个人学习分享,如有问题,欢迎指出:)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值