👋 大家好,我是 阿问学长
!专注于分享优质开源项目
解析、毕业设计项目指导
支持、幼小初高
的教辅资料
推荐等,欢迎关注交流!🚀
缓存注解与使用
前言
Spring Cache提供了强大的缓存抽象,通过注解的方式可以轻松实现缓存功能。RuoYi-Vue-Plus框架深度集成了Spring Cache和Redis,提供了完整的缓存解决方案。本文将详细介绍缓存注解的使用方法、自定义缓存Key生成策略、缓存配置优化等内容。
@Cacheable缓存注解
基础使用
@Cacheable
注解用于标记方法的返回值可以被缓存,当方法被调用时,Spring会先检查缓存中是否存在对应的数据。
@Service
public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> implements ISysUserService {
/**
* 根据用户ID查询用户信息(带缓存)
*/
@Cacheable(value = "user", key = "#userId")
public SysUserVo selectUserById(Long userId) {
SysUser user = baseMapper.selectById(userId);
if (ObjectUtil.isNull(user)) {
return null;
}
return BeanUtil.toBean(user, SysUserVo.class);
}
/**
* 根据用户名查询用户(带缓存)
*/
@Cacheable(value = "user", key = "'username:' + #userName")
public SysUser selectUserByUserName(String userName) {
LambdaQueryWrapper<SysUser> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(SysUser::getUserName, userName);
return baseMapper.selectOne(wrapper);
}
/**
* 查询用户列表(带条件缓存)
*/
@Cacheable(value = "userList", key = "#user.hashCode()", condition = "#user.status != null")
public List<SysUserVo> selectUserList(SysUserBo user) {
LambdaQueryWrapper<SysUser> wrapper = buildQueryWrapper(user);
List<SysUser> userList = baseMapper.selectList(wrapper);
return BeanUtil.copyToList(userList, SysUserVo.class);
}
/**
* 分页查询用户(带缓存)
*/
@Cacheable(value = "userPage",
key = "'page:' + #pageQuery.pageNum + ':' + #pageQuery.pageSize + ':' + #user.hashCode()",
unless = "#result.total > 1000") // 数据量大时不缓存
public TableDataInfo<SysUserVo> selectPageUserList(SysUserBo user, PageQuery pageQuery) {
LambdaQueryWrapper<SysUser> wrapper = buildQueryWrapper(user);
Page<SysUserVo> result = baseMapper.selectPageUserList(pageQuery.build(), wrapper);
return TableDataInfo.build(result);
}
/**
* 获取用户权限列表(带缓存)
*/
@Cacheable(value = "userPermissions", key = "#userId", sync = true) // 同步加载,防止缓存击穿
public Set<String> selectMenuPermsByUserId(Long userId) {
List<SysMenu> perms = menuMapper.selectMenuPermsByUserId(userId);
return perms.stream()
.filter(menu -> StringUtils.isNotEmpty(menu.getPerms()))
.map(SysMenu::getPerms)
.collect(Collectors.toSet());
}
}
条件缓存
@Service
public class SysConfigServiceImpl implements ISysConfigService {
/**
* 根据配置键查询配置值(只缓存启用的配置)
*/
@Cacheable(value = "config",
key = "#configKey",
condition = "#configKey != null && #configKey.length() > 0",
unless = "#result == null || #result.length() == 0")
public String selectConfigByKey(String configKey) {
SysConfig config = configMapper.selectOne(
new LambdaQueryWrapper<SysConfig>()
.eq(SysConfig::getConfigKey, configKey)
.eq(SysConfig::getStatus, "0") // 只查询启用的配置
);
return config != null ? config.getConfigValue() : null;
}
/**
* 获取系统配置列表(根据类型缓存)
*/
@Cacheable(value = "configList",
key = "'type:' + #configType",
condition = "#configType != null")
public List<SysConfigVo> selectConfigListByType(String configType) {
LambdaQueryWrapper<SysConfig> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(SysConfig::getConfigType, configType);
wrapper.eq(SysConfig::getStatus, "0");
wrapper.orderByAsc(SysConfig::getConfigSort);
List<SysConfig> configList = configMapper.selectList(wrapper);
return BeanUtil.copyToList(configList, SysConfigVo.class);
}
}
@CacheEvict缓存清除
基础清除操作
@Service
public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> implements ISysUserService {
/**
* 新增用户(清除相关缓存)
*/
@CacheEvict(value = {"userList", "userPage"}, allEntries = true)
public Boolean insertUser(SysUserBo user) {
SysUser sysUser = BeanUtil.toBean(user, SysUser.class);
int result = baseMapper.insert(sysUser);
if (result > 0) {
// 清除部门用户缓存
clearDeptUserCache(user.getDeptId());
}
return result > 0;
}
/**
* 修改用户信息(清除指定缓存)
*/
@CacheEvict(value = "user", key = "#user.userId")
public Boolean updateUser(SysUserBo user) {
// 获取原用户信息
SysUser oldUser = baseMapper.selectById(user.getUserId());
SysUser sysUser = BeanUtil.toBean(user, SysUser.class);
int result = baseMapper.updateById(sysUser);
if (result > 0) {
// 清除用户相关的所有缓存
clearUserRelatedCache(user.getUserId());
// 如果部门发生变化,清除相关部门缓存
if (oldUser != null && !Objects.equals(oldUser.getDeptId(), user.getDeptId())) {
clearDeptUserCache(oldUser.getDeptId());
clearDeptUserCache(user.getDeptId());
}
}
return result > 0;
}
/**
* 删除用户(多重缓存清除)
*/
@Caching(evict = {
@CacheEvict(value = "user", key = "#userId"),
@CacheEvict(value = "userPermissions", key = "#userId"),
@CacheEvict(value = {"userList", "userPage"}, allEntries = true)
})
public Boolean deleteUserById(Long userId) {
// 获取用户信息
SysUser user = baseMapper.selectById(userId);
int result = baseMapper.deleteById(userId);
if (result > 0 && user != null) {
// 清除部门用户缓存
clearDeptUserCache(user.getDeptId());
// 清除用户角色关联缓存
clearUserRoleCache(userId);
}
return result > 0;
}
/**
* 批量删除用户
*/
@CacheEvict(value = {"user", "userPermissions", "userList", "userPage"}, allEntries = true)
public Boolean deleteUserByIds(Long[] userIds) {
// 获取要删除的用户信息
List<SysUser> users = baseMapper.selectBatchIds(Arrays.asList(userIds));
int result = baseMapper.deleteBatchIds(Arrays.asList(userIds));
if (result > 0) {
// 清除相关部门缓存
Set<Long> deptIds = users.stream()
.map(SysUser::getDeptId)
.filter(Objects::nonNull)
.collect(Collectors.toSet());
for (Long deptId : deptIds) {
clearDeptUserCache(deptId);
}
}
return result > 0;
}
/**
* 清除用户相关缓存
*/
private void clearUserRelatedCache(Long userId) {
// 清除用户基本信息缓存
cacheManager.getCache("user").evict(userId);
// 清除用户权限缓存
cacheManager.getCache("userPermissions").evict(userId);
// 清除用户角色缓存
cacheManager.getCache("userRoles").evict(userId);
}
/**
* 清除部门用户缓存
*/
private void clearDeptUserCache(Long deptId) {
if (deptId != null) {
cacheManager.getCache("deptUsers").evict(deptId);
}
}
/**
* 清除用户角色缓存
*/
private void clearUserRoleCache(Long userId) {
cacheManager.getCache("userRoles").evict(userId);
}
}
条件清除
@Service
public class SysRoleServiceImpl implements ISysRoleService {
/**
* 修改角色状态(条件清除缓存)
*/
@CacheEvict(value = "roleUsers",
key = "#roleId",
condition = "#status == '1'") // 只有禁用角色时才清除缓存
public Boolean updateRoleStatus(Long roleId, String status) {
SysRole role = new SysRole();
role.setRoleId(roleId);
role.setStatus(status);
int result = roleMapper.updateById(role);
if (result > 0 && "1".equals(status)) {
// 角色被禁用时,清除所有相关用户的权限缓存
clearRoleUserPermissions(roleId);
}
return result > 0;
}
/**
* 清除角色用户权限缓存
*/
private void clearRoleUserPermissions(Long roleId) {
// 查询拥有该角色的所有用户
List<Long> userIds = userRoleMapper.selectUserIdsByRoleId(roleId);
// 清除这些用户的权限缓存
for (Long userId : userIds) {
cacheManager.getCache("userPermissions").evict(userId);
}
}
}
@CachePut缓存更新
更新缓存数据
@Service
public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> implements ISysUserService {
/**
* 更新用户信息并刷新缓存
*/
@CachePut(value = "user", key = "#user.userId")
public SysUserVo updateUserAndRefreshCache(SysUserBo user) {
SysUser sysUser = BeanUtil.toBean(user, SysUser.class);
baseMapper.updateById(sysUser);
// 返回更新后的用户信息,这个返回值会被放入缓存
SysUser updatedUser = baseMapper.selectById(user.getUserId());
return BeanUtil.toBean(updatedUser, SysUserVo.class);
}
/**
* 更新用户最后登录信息
*/
@CachePut(value = "user", key = "#userId")
public SysUserVo updateUserLoginInfo(Long userId, String loginIp, Date loginDate) {
SysUser user = new SysUser();
user.setUserId(userId);
user.setLoginIp(loginIp);
user.setLoginDate(loginDate);
baseMapper.updateById(user);
// 返回完整的用户信息用于更新缓存
SysUser updatedUser = baseMapper.selectById(userId);
return BeanUtil.toBean(updatedUser, SysUserVo.class);
}
/**
* 重置用户密码并更新缓存
*/
@CachePut(value = "user", key = "#userId", condition = "#result != null")
public SysUserVo resetUserPassword(Long userId, String newPassword) {
SysUser user = new SysUser();
user.setUserId(userId);
user.setPassword(SecurityUtils.encryptPassword(newPassword));
user.setUpdateTime(new Date());
int result = baseMapper.updateById(user);
if (result > 0) {
SysUser updatedUser = baseMapper.selectById(userId);
return BeanUtil.toBean(updatedUser, SysUserVo.class);
}
return null;
}
}
组合缓存操作
@Service
public class SysMenuServiceImpl implements ISysMenuService {
/**
* 修改菜单信息(组合缓存操作)
*/
@Caching(
put = @CachePut(value = "menu", key = "#menu.menuId"),
evict = {
@CacheEvict(value = "menuTree", allEntries = true),
@CacheEvict(value = "userMenus", allEntries = true),
@CacheEvict(value = "roleMenus", allEntries = true)
}
)
public SysMenuVo updateMenu(SysMenuBo menu) {
SysMenu sysMenu = BeanUtil.toBean(menu, SysMenu.class);
int result = menuMapper.updateById(sysMenu);
if (result > 0) {
// 如果是权限标识发生变化,需要清除所有用户权限缓存
SysMenu oldMenu = menuMapper.selectById(menu.getMenuId());
if (oldMenu != null && !Objects.equals(oldMenu.getPerms(), menu.getPerms())) {
clearAllUserPermissions();
}
SysMenu updatedMenu = menuMapper.selectById(menu.getMenuId());
return BeanUtil.toBean(updatedMenu, SysMenuVo.class);
}
return null;
}
/**
* 清除所有用户权限缓存
*/
private void clearAllUserPermissions() {
Cache userPermissionsCache = cacheManager.getCache("userPermissions");
if (userPermissionsCache != null) {
userPermissionsCache.clear();
}
}
}
自定义缓存Key生成策略
自定义KeyGenerator
/**
* 自定义缓存Key生成器
*/
@Component("customKeyGenerator")
public class CustomKeyGenerator implements KeyGenerator {
private static final String SEPARATOR = ":";
@Override
public Object generate(Object target, Method method, Object... params) {
StringBuilder key = new StringBuilder();
// 添加类名
key.append(target.getClass().getSimpleName()).append(SEPARATOR);
// 添加方法名
key.append(method.getName()).append(SEPARATOR);
// 添加参数
if (params.length > 0) {
for (Object param : params) {
if (param != null) {
key.append(param.toString()).append(SEPARATOR);
} else {
key.append("null").append(SEPARATOR);
}
}
// 移除最后一个分隔符
key.deleteCharAt(key.length() - 1);
} else {
key.append("noParams");
}
return key.toString();
}
}
/**
* 业务相关的Key生成器
*/
@Component("businessKeyGenerator")
public class BusinessKeyGenerator implements KeyGenerator {
@Override
public Object generate(Object target, Method method, Object... params) {
StringBuilder key = new StringBuilder();
// 根据方法名生成不同的Key策略
String methodName = method.getName();
if (methodName.startsWith("selectUser")) {
return generateUserKey(params);
} else if (methodName.startsWith("selectRole")) {
return generateRoleKey(params);
} else if (methodName.startsWith("selectMenu")) {
return generateMenuKey(params);
} else {
return generateDefaultKey(target, method, params);
}
}
/**
* 生成用户相关的Key
*/
private String generateUserKey(Object... params) {
StringBuilder key = new StringBuilder("user:");
for (Object param : params) {
if (param instanceof Long) {
key.append("id:").append(param);
} else if (param instanceof String) {
key.append("name:").append(param);
} else if (param instanceof SysUserBo) {
SysUserBo user = (SysUserBo) param;
key.append("query:").append(user.hashCode());
}
key.append(":");
}
return key.toString();
}
/**
* 生成角色相关的Key
*/
private String generateRoleKey(Object... params) {
StringBuilder key = new StringBuilder("role:");
for (Object param : params) {
if (param instanceof Long) {
key.append("id:").append(param);
} else if (param instanceof String) {
key.append("key:").append(param);
}
key.append(":");
}
return key.toString();
}
/**
* 生成菜单相关的Key
*/
private String generateMenuKey(Object... params) {
StringBuilder key = new StringBuilder("menu:");
for (Object param : params) {
if (param instanceof Long) {
key.append("userId:").append(param);
} else if (param instanceof String) {
key.append("type:").append(param);
}
key.append(":");
}
return key.toString();
}
/**
* 生成默认Key
*/
private String generateDefaultKey(Object target, Method method, Object... params) {
StringBuilder key = new StringBuilder();
key.append(target.getClass().getSimpleName()).append(":");
key.append(method.getName()).append(":");
for (Object param : params) {
key.append(param != null ? param.hashCode() : "null").append(":");
}
return key.toString();
}
}
使用自定义KeyGenerator
@Service
public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> implements ISysUserService {
/**
* 使用自定义Key生成器
*/
@Cacheable(value = "user", keyGenerator = "customKeyGenerator")
public SysUserVo selectUserById(Long userId) {
SysUser user = baseMapper.selectById(userId);
return BeanUtil.toBean(user, SysUserVo.class);
}
/**
* 使用业务Key生成器
*/
@Cacheable(value = "userList", keyGenerator = "businessKeyGenerator")
public List<SysUserVo> selectUserList(SysUserBo user) {
LambdaQueryWrapper<SysUser> wrapper = buildQueryWrapper(user);
List<SysUser> userList = baseMapper.selectList(wrapper);
return BeanUtil.copyToList(userList, SysUserVo.class);
}
/**
* 复杂Key生成策略
*/
@Cacheable(value = "userPage",
key = "T(com.ruoyi.common.utils.CacheKeyUtils).generatePageKey(#user, #pageQuery)")
public TableDataInfo<SysUserVo> selectPageUserList(SysUserBo user, PageQuery pageQuery) {
LambdaQueryWrapper<SysUser> wrapper = buildQueryWrapper(user);
Page<SysUserVo> result = baseMapper.selectPageUserList(pageQuery.build(), wrapper);
return TableDataInfo.build(result);
}
}
缓存Key工具类
/**
* 缓存Key工具类
*/
public class CacheKeyUtils {
private static final String SEPARATOR = ":";
/**
* 生成分页查询的Key
*/
public static String generatePageKey(Object queryObject, PageQuery pageQuery) {
StringBuilder key = new StringBuilder();
key.append("page").append(SEPARATOR);
key.append(pageQuery.getPageNum()).append(SEPARATOR);
key.append(pageQuery.getPageSize()).append(SEPARATOR);
if (StringUtils.isNotBlank(pageQuery.getOrderByColumn())) {
key.append("order").append(SEPARATOR);
key.append(pageQuery.getOrderByColumn()).append(SEPARATOR);
key.append(pageQuery.getIsAsc()).append(SEPARATOR);
}
key.append("query").append(SEPARATOR);
key.append(queryObject.hashCode());
return key.toString();
}
/**
* 生成用户相关的Key
*/
public static String generateUserKey(String prefix, Object... params) {
StringBuilder key = new StringBuilder();
key.append("user").append(SEPARATOR);
if (StringUtils.isNotBlank(prefix)) {
key.append(prefix).append(SEPARATOR);
}
for (Object param : params) {
if (param != null) {
key.append(param.toString()).append(SEPARATOR);
}
}
return key.toString();
}
/**
* 生成租户相关的Key
*/
public static String generateTenantKey(String tenantId, String prefix, Object... params) {
StringBuilder key = new StringBuilder();
key.append("tenant").append(SEPARATOR);
key.append(tenantId).append(SEPARATOR);
if (StringUtils.isNotBlank(prefix)) {
key.append(prefix).append(SEPARATOR);
}
for (Object param : params) {
if (param != null) {
key.append(param.toString()).append(SEPARATOR);
}
}
return key.toString();
}
/**
* 生成带时间戳的Key(用于定时失效)
*/
public static String generateTimeBasedKey(String prefix, long timeWindow, Object... params) {
StringBuilder key = new StringBuilder();
key.append(prefix).append(SEPARATOR);
// 时间窗口(例如:每小时、每天)
long timeSlot = System.currentTimeMillis() / timeWindow;
key.append(timeSlot).append(SEPARATOR);
for (Object param : params) {
if (param != null) {
key.append(param.toString()).append(SEPARATOR);
}
}
return key.toString();
}
}
缓存配置优化
缓存管理器配置
/**
* 缓存管理器配置
*/
@Configuration
@EnableCaching
public class CacheConfig {
@Autowired
private RedisConnectionFactory redisConnectionFactory;
/**
* 缓存管理器
*/
@Bean
@Primary
public CacheManager cacheManager() {
RedisCacheManager.Builder builder = RedisCacheManager.builder(redisConnectionFactory)
.cacheDefaults(getCacheConfiguration(Duration.ofMinutes(30))) // 默认30分钟过期
.transactionAware(); // 支持事务
// 配置不同缓存的过期时间
Map<String, RedisCacheConfiguration> cacheConfigurations = new HashMap<>();
// 用户信息缓存 - 1小时
cacheConfigurations.put("user", getCacheConfiguration(Duration.ofHours(1)));
// 用户权限缓存 - 30分钟
cacheConfigurations.put("userPermissions", getCacheConfiguration(Duration.ofMinutes(30)));
// 配置信息缓存 - 12小时
cacheConfigurations.put("config", getCacheConfiguration(Duration.ofHours(12)));
// 字典数据缓存 - 6小时
cacheConfigurations.put("dict", getCacheConfiguration(Duration.ofHours(6)));
// 菜单树缓存 - 2小时
cacheConfigurations.put("menuTree", getCacheConfiguration(Duration.ofHours(2)));
// 分页数据缓存 - 10分钟
cacheConfigurations.put("userPage", getCacheConfiguration(Duration.ofMinutes(10)));
cacheConfigurations.put("userList", getCacheConfiguration(Duration.ofMinutes(15)));
builder.withInitialCacheConfigurations(cacheConfigurations);
return builder.build();
}
/**
* 获取缓存配置
*/
private RedisCacheConfiguration getCacheConfiguration(Duration ttl) {
return RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(ttl) // 设置过期时间
.serializeKeysWith(RedisSerializationContext.SerializationPair
.fromSerializer(new StringRedisSerializer())) // Key序列化
.serializeValuesWith(RedisSerializationContext.SerializationPair
.fromSerializer(new GenericJackson2JsonRedisSerializer())) // Value序列化
.disableCachingNullValues() // 不缓存null值
.computePrefixWith(cacheName -> "ruoyi:cache:" + cacheName + ":"); // 缓存前缀
}
/**
* 自定义缓存错误处理器
*/
@Bean
public CacheErrorHandler cacheErrorHandler() {
return new CacheErrorHandler() {
@Override
public void handleCacheGetError(RuntimeException exception, Cache cache, Object key) {
log.error("缓存获取异常 - cache: {}, key: {}", cache.getName(), key, exception);
}
@Override
public void handleCachePutError(RuntimeException exception, Cache cache, Object key, Object value) {
log.error("缓存存储异常 - cache: {}, key: {}", cache.getName(), key, exception);
}
@Override
public void handleCacheEvictError(RuntimeException exception, Cache cache, Object key) {
log.error("缓存清除异常 - cache: {}, key: {}", cache.getName(), key, exception);
}
@Override
public void handleCacheClearError(RuntimeException exception, Cache cache) {
log.error("缓存清空异常 - cache: {}", cache.getName(), exception);
}
};
}
}
总结
本文详细介绍了Spring Cache缓存注解的使用,包括:
- @Cacheable注解:缓存方法返回值,支持条件缓存和同步加载
- @CacheEvict注解:清除缓存数据,支持单个清除和批量清除
- @CachePut注解:更新缓存数据,确保缓存与数据库同步
- 自定义Key生成策略:灵活的缓存Key生成机制
- 缓存配置优化:不同类型数据的差异化缓存策略
通过合理使用缓存注解,可以显著提升应用性能,减少数据库访问压力。
在下一篇文章中,我们将探讨Redis分布式锁与限流的实现。