当前位置: 首页 > article >正文

Mysql--实战篇--@Transactional失效场景及避免策略(@Transactional实现原理,失效场景,内部调用问题等)

在Spring框架中,@Transactional注解用于声明式事务管理,能够简化事务的处理逻辑。然而,在某些情况下,@Transactional可能会失效,导致事务无法按预期工作。了解这些失效场景及其原因,可以帮助你更好地管理和调试事务问题。

1、@Transactional失效的常见场景

(1)、方法非public访问权限

@Transactional注解通常只能应用于public方法上。如果将其应用于protected、private或包级私有方法上,由于Spring的代理机制无法拦截这些方法的调用,因此事务注解将失效。

(2)、同一个类的内部调用

当@Transactional注解的方法在同一类内部被另一个方法调用时,事务可能会失效。这是因为Spring的AOP(面向切面编程)机制是通过代理对象来实现事务管理的。只有当外部类通过代理对象调用带有@Transactional注解的方法时,Spring才会拦截该方法并为其创建事务。而在同一类内部直接调用方法时,不会经过代理对象,因此事务不会生效。

内部调用示例:

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    // 带有@Transactional注解的方法
    @Transactional
    public void createUser(User user) {
        userRepository.save(user);
    }

    // 同一类内部调用带有@Transactional注解的方法
    public void createUserInternal() {
        User user = new User();
        user.setName("Alice");
        createUser(user);      // 事务不会生效
    }
}

解释:
在上面的例子中,createUser方法虽然带有@Transactional注解,但在createUserInternal方法中直接调用了createUser,这不会触发Spring的事务管理机制。因为createUserInternal和createUser是同一个类的方法,调用是通过this引用进行的,而不是通过代理对象调用的,因此事务不会生效。

解决方案:

  • 拆分到不同类:将createUser方法移到另一个服务类中,确保它是通过代理对象调用的。
    示例:
  @Service
  public class UserService {

      @Autowired
      private UserRepository userRepository;

      @Transactional
      public void createUser(User user) {
          userRepository.save(user);
      }
  }

  @Service
  public class AnotherService {

      @Autowired
      private UserService userService;

      public void createUserInternal() {
          User user = new User();
          user.setName("Alice");
          userService.createUser(user);  // 通过代理对象调用,事务生效
      }
  }
  • 使用自定义代理:可以通过AopContext.currentProxy()获取当前类的代理对象,然后通过代理对象调用方法。
    示例:
  @Service
  public class UserService {

      @Autowired
      private UserRepository userRepository;

      @Transactional
      public void createUser(User user) {
          userRepository.save(user);
      }

      public void createUserInternal() {
          User user = new User();
          user.setName("Alice");
          ((UserService) AopContext.currentProxy()).createUser(user);  // 使用代理对象调用
      }
  }

(3)、事务管理器配置错误

如果Spring容器中配置了多个事务管理器,但在使用@Transactional注解时没有明确指定事务管理器,可能会导致Spring使用默认的事务管理器,而这个默认的事务管理器可能不适用于当前的操作,从而导致事务注解失效。

(4)、方法内部捕捉异常

在使用@Transactional注解的方法中,如果内部捕获了可能导致事务回滚的异常,并且没有重新抛出一个Spring框架能够识别的运行时异常或声明式异常,那么事务管理器将无法感知到异常,从而可能导致事务不会回滚。
@Transactional注解默认只会对RuntimeException和未检查的异常进行回滚。如果在事务方法中捕获了异常并吞掉(即没有抛出或记录),事务将不会回滚,导致数据不一致。

错误示例:

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    @Transactional
    public void createUser(User user) {
        try {
            userRepository.save(user);
            // 模拟异常
            if (user.getName().equals("Alice")) {
                throw new RuntimeException("模拟异常");
            }
        } catch (Exception e) {
            // 异常被捕获并吞掉,事务不会回滚
            System.out.println("捕获到异常:" + e.getMessage());
        }
    }
}

解释:
在上面的例子中,当user.getName()等于"Alice"时,会抛出一个RuntimeException,但这个异常被捕获并在catch块中处理,没有重新抛出。由于@Transactional注解默认只对未捕获的RuntimeException进行回滚,因此事务不会回滚,导致数据不一致。

解决方案:

  • 不要吞掉异常:确保在catch块中记录异常日志,并根据需要重新抛出异常。
    示例:
  @Transactional
  public void createUser(User user) {
      try {
          userRepository.save(user);
          if (user.getName().equals("Alice")) {
              throw new RuntimeException("模拟异常");
          }
      } catch (Exception e) {
          // 记录异常日志
          logger.error("创建用户时发生异常", e);
          // 重新抛出异常,确保事务回滚
          throw e;
      }
  }
  • 手动标记事务为回滚:如果不希望重新抛出异常,可以使用TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()手动标记事务为回滚。
    示例:
  import org.springframework.transaction.interceptor.TransactionAspectSupport;

  @Transactional
  public void createUser(User user) {
      try {
          userRepository.save(user);
          if (user.getName().equals("Alice")) {
              throw new RuntimeException("模拟异常");
          }
      } catch (Exception e) {
          // 手动标记事务为回滚
          TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
          logger.error("创建用户时发生异常", e);
      }
  }

(5)、使用final修饰的方法

如果使用final关键字修饰了方法,那么由于该方法不能被重写,Spring的代理机制将无法对其应用@Transactional注解,因此事务将失效。

(6)、静态方法

静态方法同样无法通过动态代理来应用@Transactional注解,因为静态方法不属于类的实例方法,而是属于类本身。

(7)、未被Spring管理的类

如果一个类没有被Spring管理(即没有使用@Controller、@Service、@Component、@Repository等注解进行标注),那么该类中的方法即使使用了@Transactional注解也不会生效。

(8)、传播特性配置错误

@Transactional 注解支持多种事务传播行为(Propagation),不同的传播行为会影响事务的创建和管理方式。如果不正确配置传播行为,可能会导致事务不按预期工作。
可以指定propagation参数来定义事务的传播行为。如果传播特性配置错误(例如设置为Propagation.NEVER,而当前存在事务),则事务将不会生效。

常见的传播行为:

  • REQUIRED(默认):如果当前存在事务,则加入该事务;否则创建一个新的事务。
  • REQUIRES_NEW:总是创建一个新的事务,如果当前存在事务,则将其挂起。
  • SUPPORTS:如果当前存在事务,则加入该事务;否则以非事务方式执行。
  • NOT_SUPPORTED:以非事务方式执行,如果当前存在事务,则将其挂起。
  • MANDATORY:必须在一个现有的事务中执行,否则抛出异常。
  • NEVER:必须在没有事务的情况下执行,否则抛出异常。
  • NESTED:如果当前存在事务,则在嵌套事务内执行;否则创建一个新的事务。

示例:

@Service
public class UserService {

    @Transactional(propagation = Propagation.REQUIRED)
    public void methodA() {
        // 执行一些数据库操作
        methodB();
    }

    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public void methodB() {
        // 这里的事务不会生效,因为propagation设置为NOT_SUPPORTED
        // 任何数据库操作都不会参与事务
    }
}

解决方案:

  • 根据业务需求选择合适的传播行为。例如,如果你希望methodB与methodA共享同一个事务,应该使用REQUIRED或REQUIRES_NEW,而不是NOT_SUPPORTED或NEVER。
    改进后的代码:
  @Service
  public class UserService {

      @Transactional(propagation = Propagation.REQUIRED)
      public void methodA() {
          // 执行一些数据库操作
          methodB();
      }

      @Transactional(propagation = Propagation.REQUIRED)
      public void methodB() {
          // 现在 methodB 会与 methodA 共享同一个事务
      }
  }

(9)、数据库不支持事务

如果使用的数据库表不支持事务(例如,某些类型的存储引擎或数据库系统不支持事务),那么即使使用了@Transactional注解,事务也不会生效。

(10)、异步线程调用

在Spring中,@Async注解用于开启异步任务执行。默认情况下,@Async和@Transactional不能同时生效。这是因为在异步任务中,Spring的事务管理器无法正确管理事务,导致事务失效。

异常示例:

@Service
public class AsyncService {

    @Autowired
    private UserRepository userRepository;

    @Async
    @Transactional
    public void createUserAsync(User user) {
        userRepository.save(user);
        // 模拟异常
        if (user.getName().equals("Alice")) {
            throw new RuntimeException("模拟异常");
        }
    }
}

解释:
在上面的例子中,createUserAsync方法同时标注了@Async和@Transactional。由于@Async注解会将方法的执行交给一个新的线程池,而Spring的事务管理器是基于主线程的,因此在异步任务中,事务管理器无法正确管理事务,导致事务失效。

解决方案:

  • 使用Propagation.REQUIRES_NEW:可以在异步方法中使用Propagation.REQUIRES_NEW来强制创建一个新的事务。这样即使在异步任务中,事务也能正常工作。
    示例:
  @Async
  @Transactional(propagation = Propagation.REQUIRES_NEW)  // 强制执行事务
  public void createUserAsync(User user) {
      userRepository.save(user);
      if (user.getName().equals("Alice")) {
          throw new RuntimeException("模拟异常");
      }
  }
  • 避免在异步方法中使用事务:如果异步任务不需要事务支持,建议将事务逻辑移到同步方法中,或者在异步任务中手动管理事务。

(11)、事务回滚规则设置不当

@Transactional注解允许你指定哪些异常会导致事务回滚(rollbackFor)和哪些异常不会导致事务回滚(noRollbackFor)。如果不正确配置这些规则,可能会导致事务在不应该回滚的情况下回滚,或者在应该回滚的情况下没有回滚。

示例:

@Service
public class UserService {

    @Transactional(rollbackFor = Exception.class)
    public void updateUser(User user) {
        // 执行更新操作
        userMapper.updateUser(user);
        // 抛出一个自定义异常
        throw new CustomException("自定义异常");
    }

    public class CustomException extends Exception {
        public CustomException(String message) {
            super(message);
        }
    }
}

解决方案:

  • 根据业务需求合理配置rollbackFor和noRollbackFor。通常情况下,@Transactional默认只会对RuntimeException和其子类进行回滚。如果你希望对其他类型的异常也进行回滚,可以使用rollbackFor指定具体的异常类型。
    改进后的代码:
  @Service
  public class UserService {

      @Transactional(rollbackFor = {CustomException.class, RuntimeException.class})
      public void updateUser(User user) {
          // 执行更新操作
          userMapper.updateUser(user);
          // 抛出一个自定义异常
          throw new CustomException("自定义异常");
      }

      public class CustomException extends Exception {
          public CustomException(String message) {
              super(message);
          }
      }
  }

如果你不希望某些异常导致事务回滚,可以使用 noRollbackFor:

  @Service
  public class UserService {

      @Transactional(noRollbackFor = CustomException.class)
      public void updateUser(User user) {
          // 执行更新操作
          userMapper.updateUser(user);
          // 抛出一个自定义异常
          throw new CustomException("自定义异常");
      }

      public class CustomException extends Exception {
          public CustomException(String message) {
              super(message);
          }
      }
  }

2、避免@Transactional注解失效策略

(1)、确保方法是public的。
(2)、避免在同一个类中直接调用其他事务方法(可以通过注入自身的方式来解决)。
(3)、正确配置事务管理器。
(4)、不要在事务方法内部捕获并处理可能导致事务回滚的异常(或者重新抛出一个Spring框架能够识别的异常)。
(5)、避免使用final和static修饰事务方法。
(6)、确保类被Spring管理。
(7)、正确配置事务的传播特性。
(8)、使用支持事务的数据库表和存储引擎。
(9)、在多线程环境下,确保事务方法在同一个线程中执行。

通过遵循这些原则,可以最大程度地确保@Transactional注解在Spring框架中的正确性和有效性。

3、@Transactional实现原理

在Spring框架中,@Transactional注解用于声明式事务管理,它通过AOP(面向切面编程)来实现。Spring使用代理机制来拦截带有@Transactional注解的方法调用,并在其周围添加事务管理逻辑。

(1)、Spring创建代理对象

当Spring容器启动时,它会扫描所有容器中带有@Transactional 注解的方法,并为这些方法创建一个代理对象。这个代理对象会拦截对原始业务逻辑类的调用,并在方法执行前后添加事务管理逻辑。

这个代理对象会在方法调用前后执行以下操作:

  • 开启事务:在方法执行之前,Spring会检查当前是否存在事务。如果不存在,则创建一个新的事务。
  • 提交或回滚事务:在方法执行完毕后,Spring会根据方法的执行结果决定是提交还是回滚事务。如果方法正常结束,则提交事务;如果方法抛出异常,则根据配置决定是否回滚事务。
  • 传播行为:@Transactional注解还支持多种传播行为(如REQUIRED、REQUIRES_NEW等),决定了如何处理现有事务或创建新事务。

创建代理对象的方式:

  • CGLIB动态代理(默认方式)
    如果目标类没有实现接口,Spring会使用CGLIB动态代理来生成代理类。CGLIB通过继承目标类并重写其方法来实现代理。
  • JDK动态代理
    如果目标类实现了接口,Spring会优先使用JDK动态代理。JDK动态代理通过实现接口并使用InvocationHandler来拦截方法调用。

(2)、@Transactional伪代码

下面是 @Transactional 代理机制的伪代码描述,展示了 Spring 如何通过代理对象来管理事务的生命周期。

示例:

// 假设这是 Spring 创建的代理对象
public class UserServiceProxy implements InvocationHandler {

    // 目标对象(原始的 UserService 实例)
    private final Object target;

    // 事务管理器
    private final PlatformTransactionManager transactionManager;

    // 事务属性(从 @Transactional 注解中读取)
    private final TransactionAttribute transactionAttribute;

    public UserServiceProxy(Object target, PlatformTransactionManager transactionManager, TransactionAttribute transactionAttribute) {
        this.target = target;
        this.transactionManager = transactionManager;
        this.transactionAttribute = transactionAttribute;
    }

    // 拦截对目标对象的方法调用
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 检查是否是需要事务管理的方法
        if (method.isAnnotationPresent(Transactional.class)) {
            return executeWithTransaction(method, args);
        } else {
            // 如果不是事务方法,直接调用目标对象的方法
            return method.invoke(target, args);
        }
    }

    // 执行带有事务管理的方法
    private Object executeWithTransaction(Method method, Object[] args) throws Throwable {
        // 1. 获取当前事务状态
        TransactionStatus status = null;
        try {
            // 2. 开启新事务或加入现有事务
            status = transactionManager.getTransaction(transactionAttribute);

            // 3. 调用目标对象的业务逻辑方法
            Object result = method.invoke(target, args);

            // 4. 提交事务
            transactionManager.commit(status);

            // 5. 返回业务方法的结果
            return result;
        } catch (Exception e) {
            // 6. 如果发生异常,回滚事务
            if (status != null) {
                transactionManager.rollback(status);
            }
            // 7. 抛出异常,让调用者处理
            throw e;
        } finally {
            // 8. 清理资源(如关闭连接等)
            // 这一步通常由连接池自动处理
        }
    }
}

// 假设这是 Spring 容器中的代理工厂
public class TransactionalProxyFactory {
    public Object createProxy(Object target, Class<?>[] interfaces, PlatformTransactionManager transactionManager, TransactionAttribute transactionAttribute) {
        // 使用 JDK 动态代理
        if (interfaces.length > 0) {
            return Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                interfaces,
                new UserServiceProxy(target, transactionManager, transactionAttribute)
            );
        }
        // 使用 CGLIB 动态代理
        else {
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(target.getClass());
            enhancer.setCallback(new UserServiceProxy(target, transactionManager, transactionAttribute));
            return enhancer.create();
        }
    }
}

(3)、实例分析解释

通过使用自我注入(Self-Injection)方式来解释下:

示例:

@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private UserService self;  // 自我注入

    @Transactional
    public void createUser(User user) {
        // 业务逻辑:插入用户数据
        userMapper.insertUser(user);
        
        // 通过代理对象调用 updateUser 方法
        self.updateUser(user);
    }

    @Transactional
    public void updateUser(User user) {
        // 业务逻辑:更新用户数据
        userMapper.updateUser(user);
    }
}

解释:

  • Spring服务在创建容器时会自动扫描@Service,@Component,@Transactional等注解,并在容器中创建实例对象。在使用的地方通过@Autowired或@Resource注解可以拿出该对象的代理对象使用(注意此时拿出来的是代理对象,而不是原始对象)。
  • 针对@Transactional注解,spring内部通过aop的方式对注解方法做了围绕增强,如帮我们开启事务,结束帮我们提交事务等,如第二部分的伪代码。
  • 在本例中,本身就是UserService,内部在注入UserService self对象,这两个对象都是实现一样的功能,只不过self对象通过@Autowired注入,实际为UserService的代理对象。
    如果类中直接使用自身的updateUser方法,属于内部直接调用的范畴。如果使用self对象调用updateUser方法,则是通过代理对象实现的。代理对象会执行aop使事务生效。内部调用没有调用aop,所以事务就不会起作用了。

乘风破浪会有时,直挂云帆济沧海!!!


http://www.kler.cn/a/511334.html

相关文章:

  • C#中System.Text.Json:从入门到精通的实用指南
  • 电子电气架构 --- ECU故障诊断指南
  • 天机学堂5-XxlJobRedis
  • RabbitMQ-消息可靠性以及延迟消息
  • Spring Web MVC综合案例
  • mysql 与Redis 数据强一致方案
  • 深度学习 DAY1:RNN 神经网络及其变体网络(LSTM、GRU)
  • Spring AI入门示例HelloWorld
  • 基于微信小程序的购物系统设计与实现(LW+源码+讲解)
  • 【江西新能源科技职业学院主办 | JPCS(ISSN: 1742-6588)出版,快速见刊检索】2025年可再生能源与节能国际会议(REEC 2025)
  • Windows中安装RabbitMQ
  • TrueNas Webui页面自定义
  • 2024年度总结:从后端Java到全栈成长的蜕变
  • python3GUI--仿崩坏三二次元登录页面(附下载地址) By:PyQt5
  • 如何优化前端性能,减少页面加载时间?
  • 解决 IntelliJ IDEA 项目包后出现“% classes”和“% lines covered”的问题
  • 在 Ubuntu 上安装 Nginx 的详细指南
  • 智能阅读时代:基于NLP的自动新闻摘要技术解析
  • 【VRChat · 改模】Unity工程导入人物模型;并添加着色器教程;
  • 简述mysql 主从复制原理及其工作过程,配置一主两从并验证
  • 论文阅读(一):下一代基因组学和遗传学的概率图模型
  • 简述mysql 主从复制原理及其工作过程,配置一主两从并验证。
  • 利用免费GIS工具箱实现高斯泼溅切片,将 PLY 格式转换为 3dtiles
  • 使用numpy求解线性代数相关问题
  • HTML<center>标签
  • CTF随题思路—[BSidesSF2019]zippy