Ruoyi-vue-plus-5.x第三篇Redis缓存与分布式技术:3.2 缓存注解与使用
👋 大家好,我是 阿问学长
!专注于分享优质开源项目
解析、毕业设计项目指导
支持、幼小初高
的教辅资料
推荐等,欢迎关注交流!🚀
缓存注解与使用
前言
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 = ":";@Overridepublic 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 {@Overridepublic 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 {@Autowiredprivate RedisConnectionFactory redisConnectionFactory;/*** 缓存管理器*/@Bean@Primarypublic 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 + ":"); // 缓存前缀}/*** 自定义缓存错误处理器*/@Beanpublic CacheErrorHandler cacheErrorHandler() {return new CacheErrorHandler() {@Overridepublic void handleCacheGetError(RuntimeException exception, Cache cache, Object key) {log.error("缓存获取异常 - cache: {}, key: {}", cache.getName(), key, exception);}@Overridepublic void handleCachePutError(RuntimeException exception, Cache cache, Object key, Object value) {log.error("缓存存储异常 - cache: {}, key: {}", cache.getName(), key, exception);}@Overridepublic void handleCacheEvictError(RuntimeException exception, Cache cache, Object key) {log.error("缓存清除异常 - cache: {}, key: {}", cache.getName(), key, exception);}@Overridepublic void handleCacheClearError(RuntimeException exception, Cache cache) {log.error("缓存清空异常 - cache: {}", cache.getName(), exception);}};}
}
总结
本文详细介绍了Spring Cache缓存注解的使用,包括:
- @Cacheable注解:缓存方法返回值,支持条件缓存和同步加载
- @CacheEvict注解:清除缓存数据,支持单个清除和批量清除
- @CachePut注解:更新缓存数据,确保缓存与数据库同步
- 自定义Key生成策略:灵活的缓存Key生成机制
- 缓存配置优化:不同类型数据的差异化缓存策略
通过合理使用缓存注解,可以显著提升应用性能,减少数据库访问压力。
在下一篇文章中,我们将探讨Redis分布式锁与限流的实现。
参考资料
- Spring Cache官方文档
- Redis缓存最佳实践
- RuoYi-Vue-Plus缓存实现源码