@Transactional 事务不要滥用。事务会影响数据库的 QPS,另外使用事务的地方需
要考虑各方面的回滚方案,包括缓存回滚、搜索引擎回滚、消息补偿、统计修正等
在使用事务时,确保在适当的情况下使用,并且要考虑到各方面的回滚方案。以下是一些事务回滚的方案:
数据库回滚:当事务出现问题时,最基本的回滚方案是回滚数据库。这可以通过将所有修改操作封装在一个事务中,并在发生错误时回滚整个事务来实现。
缓存回滚:如果事务涉及到缓存,如 Redis 或 Memcached,确保在事务提交之前,所有缓存修改都已被缓存。如果发生回滚,必须撤销所有对缓存的更改,以确保缓存的一致性。
搜索引擎回滚:如果事务需要将数据索引到搜索引擎中,则需要确保索引操作是可撤销的。当事务回滚时,索引必须回滚以确保搜索结果的一致性。
消息补偿:如果事务涉及发送消息,则必须确保消息是幂等的,这意味着同一消息可以重复发送而不会导致不一致性。在回滚时,需要发送一个补偿消息以撤销任何已发送的消息。
统计修正:如果事务影响到某些统计数据,如计数器或聚合数据,则必须确保在回滚时更新这些数据。这可以通过维护一个历史记录并在回滚时重新计算数据来实现。
综上所述,确保在适当的情况下使用事务,并考虑各方面的回滚方案是非常重要的。根据实际情况选择适当的方案可以确保数据的一致性和可靠性。
当在 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
的服务类,并在其中调用 userService
的 createUser
方法。在 try-catch
代码块中,我们捕获异常并调用 userService
的 evictUserCache
方法,以确保在事务回滚时清空缓存。
请注意,我们在 AnotherService
类中调用 evictUserCache
方法时,AOP 代理将被触发,从而确保缓存清空操作生效。