Spring Boot集成Redis时@Cacheable的可用位置及缓存失效问题排查
Spring Boot中@Cacheable的应用场景与你的缓存失效问题解析
嘿,我来帮你拆解这个问题,先从@Cacheable的适用范围说起,再分析你的代码问题,最后给你可行的解决方案:
一、@Cacheable不能用在任意方法!它的有效应用位置是这些:
Spring的缓存注解是基于AOP代理实现的,所以只有满足以下条件的方法才能触发缓存逻辑:
- 公共(public)的实例方法:这是最标准的用法,只有外部通过Spring容器管理的Bean实例调用public方法时,代理才会拦截请求并执行缓存的读取/写入逻辑。
- 接口的实现方法:如果你的类实现了某个接口,并且Spring使用JDK动态代理(默认针对接口的代理方式),那么接口方法上标注@Cacheable也会生效。
而像private、protected方法,或者同一个类内部调用的public方法,默认都无法触发缓存——因为这些调用没有经过Spring的代理对象,注解的逻辑根本不会被执行。
二、你的Redis缓存未生效的核心原因
你把@Cacheable标注在了private User findUser(Long userID)这个私有方法上,这直接踩了Spring缓存的“坑”:
当你在findByUserID方法里直接调用findUser时,这是类内部的直接方法调用,完全绕开了Spring为这个类创建的代理对象。所以@Cacheable的逻辑根本没被触发,自然Redis里不会有任何缓存数据。
三、解决方法:给缓存逻辑“搭个代理的桥”
这里有几个可行的方案,你可以根据自己的代码结构选择:
方案1:把findUser改为public方法,通过自身代理对象调用
把私有方法改为public,然后在类内部注入自身的代理Bean,用代理对象调用这个方法,让缓存逻辑生效:
@Service // 假设这是Spring管理的Service类 public class UserService { @Autowired private UserService selfProxy; // 注入自身的代理对象 private final UserRepository userRepository; private final PasswordRepository passwordRepository; private final UserMapper userMapper; // 构造方法注入依赖(推荐构造注入) public UserService(UserRepository userRepository, PasswordRepository passwordRepository, UserMapper userMapper) { this.userRepository = userRepository; this.passwordRepository = passwordRepository; this.userMapper = userMapper; } public UserDTO findByUserID(Long userID) { User user = selfProxy.findUser(userID); // 通过代理对象调用,触发缓存 if (user != null) { Password password = findPassword(userID); return userMapper.mapToDTO(user, password); } return null; } private Password findPassword(Long userID) { return passwordRepository.findPasswordBasedOnUserID(userID); } @Cacheable(value = "users", key = "#userID") public User findUser(Long userID) { // 改为public方法 return userRepository.findByUserID(userID); } }
方案2:直接把@Cacheable加在findByUserID方法上
如果你的UserDTO适合作为缓存对象(比如DTO的结构稳定,不需要频繁拆分实体查询),可以直接把缓存注解加在对外的public方法上,这样整个DTO的查询结果都会被缓存:
@Service public class UserService { private final UserRepository userRepository; private final PasswordRepository passwordRepository; private final UserMapper userMapper; public UserService(UserRepository userRepository, PasswordRepository passwordRepository, UserMapper userMapper) { this.userRepository = userRepository; this.passwordRepository = passwordRepository; this.userMapper = userMapper; } @Cacheable(value = "userDTOs", key = "#userID") public UserDTO findByUserID(Long userID) { User user = findUser(userID); if (user != null) { Password password = findPassword(userID); return userMapper.mapToDTO(user, password); } return null; } // 这里findUser可以保持private,因为缓存直接作用于DTO的查询 private User findUser(Long userID) { return userRepository.findByUserID(userID); } private Password findPassword(Long userID) { return passwordRepository.findPasswordBasedOnUserID(userID); } }
方案3:使用Spring的@Lookup方法(进阶用法)
如果不想注入自身代理,也可以用@Lookup注解来获取代理对象,适合一些特殊场景:
@Service public class UserService { private final UserRepository userRepository; private final PasswordRepository passwordRepository; private final UserMapper userMapper; public UserService(UserRepository userRepository, PasswordRepository passwordRepository, UserMapper userMapper) { this.userRepository = userRepository; this.passwordRepository = passwordRepository; this.userMapper = userMapper; } public UserDTO findByUserID(Long userID) { User user = getProxy().findUser(userID); if (user != null) { Password password = findPassword(userID); return userMapper.mapToDTO(user, password); } return null; } @Lookup protected UserService getProxy() { return null; // 这个方法会被Spring动态实现,返回代理对象 } @Cacheable(value = "users", key = "#userID") public User findUser(Long userID) { return userRepository.findByUserID(userID); } private Password findPassword(Long userID) { return passwordRepository.findPasswordBasedOnUserID(userID); } }
内容的提问来源于stack exchange,提问作者enthusiastdev
相关产品推荐
相关产品推荐

