@Transactional 事务不要滥用, 要考虑各方面的回滚方案哦

@Transactional 事务不要滥用。事务会影响数据库的 QPS,另外使用事务的地方需
要考虑各方面的回滚方案,包括缓存回滚、搜索引擎回滚、消息补偿、统计修正等

在使用事务时,确保在适当的情况下使用,并且要考虑到各方面的回滚方案。以下是一些事务回滚的方案:

  1. 数据库回滚:当事务出现问题时,最基本的回滚方案是回滚数据库。这可以通过将所有修改操作封装在一个事务中,并在发生错误时回滚整个事务来实现。

  2. 缓存回滚:如果事务涉及到缓存,如 Redis 或 Memcached,确保在事务提交之前,所有缓存修改都已被缓存。如果发生回滚,必须撤销所有对缓存的更改,以确保缓存的一致性。

  3. 搜索引擎回滚:如果事务需要将数据索引到搜索引擎中,则需要确保索引操作是可撤销的。当事务回滚时,索引必须回滚以确保搜索结果的一致性。

  4. 消息补偿:如果事务涉及发送消息,则必须确保消息是幂等的,这意味着同一消息可以重复发送而不会导致不一致性。在回滚时,需要发送一个补偿消息以撤销任何已发送的消息。

  5. 统计修正:如果事务影响到某些统计数据,如计数器或聚合数据,则必须确保在回滚时更新这些数据。这可以通过维护一个历史记录并在回滚时重新计算数据来实现。

综上所述,确保在适当的情况下使用事务,并考虑各方面的回滚方案是非常重要的。根据实际情况选择适当的方案可以确保数据的一致性和可靠性。

缓存回滚

当在 Spring Boot 项目中使用缓存时,如果在 @Transactional 方法中发生异常导致事务回滚,那么之前已经写入缓存中的数据需要撤销或回滚。以下是一些示例代码说明如何实现缓存回滚:

假设我们有一个名为 UserService 的服务类,其中有一个名为 createUser 的方法,该方法将新用户保存到数据库并将用户数据写入 Redis 缓存中。示例代码如下:

@Service
public class UserService {
    
    
  
  @Autowired
  private UserRepository userRepository;
  
  @Autowired
  private RedisTemplate<String, Object> redisTemplate;
  
  @Transactional
  public void createUser(User user) {
    
    
    userRepository.save(user);
    redisTemplate.opsForValue().set("user:" + user.getId(), user);
    // 抛出异常,触发事务回滚
    throw new RuntimeException("Failed to create user");
  }
  
}

在上面的代码中,createUser 方法在将用户保存到数据库后,将用户写入 Redis 缓存中。如果在写入缓存之后出现异常,事务将回滚并且之前写入的缓存也需要被回滚。这可以通过添加 @CacheEvict 注解来实现,该注解会在方法执行后清空指定的缓存。修改后的代码如下所示:

@Service
public class UserService {
    
    
  
  @Autowired
  private UserRepository userRepository;
  
  @Autowired
  private RedisTemplate<String, Object> redisTemplate;
  
  @Transactional
  public void createUser(User user) {
    
    
    userRepository.save(user);
    redisTemplate.opsForValue().set("user:" + user.getId(), user);
    // 抛出异常,触发事务回滚
    throw new RuntimeException("Failed to create user");
  }
  
  @CacheEvict(value = "users", key = "#user.id")
  public void evictUserCache(User user) {
    
    
    // 此方法仅用于清空缓存,不需要实现任何逻辑
  }
  
}

@CacheEvict 注解是通过 AOP 实现的,当我们在同一个类中直接调用缓存清空方法时,AOP 代理将不会被触发,因此 @CacheEvict 注解也不会生效。

在我们的示例中,如果我们直接在 createUser 方法中调用 evictUserCache(user),那么缓存清空操作将不会被触发。

为了确保缓存清空操作生效,我们需要通过在其他类中调用 evictUserCache 方法来触发 AOP 代理。例如,我们可以在另一个服务类中调用 evictUserCache 方法,以便在事务回滚时清空缓存。示例代码如下:

@Service
public class AnotherService {
    
    
  
  @Autowired
  private UserService userService;
  
  @Transactional
  public void createUser(User user) {
    
    
    try {
    
    
      userService.createUser(user);
    } catch (Exception e) {
    
    
      userService.evictUserCache(user);
      throw e;
    }
  }
  
}

在上面的代码中,我们创建了一个名为 AnotherService 的服务类,并在其中调用 userServicecreateUser 方法。在 try-catch 代码块中,我们捕获异常并调用 userServiceevictUserCache 方法,以确保在事务回滚时清空缓存。

请注意,我们在 AnotherService 类中调用 evictUserCache 方法时,AOP 代理将被触发,从而确保缓存清空操作生效。

猜你喜欢

转载自blog.csdn.net/HongZeng_CSDN/article/details/130042039