Ruoyi-vue-plus-5.x第三篇Redis缓存与分布式技术:3.2 缓存注解与使用

发布于:2025-09-03 ⋅ 阅读:(12) ⋅ 点赞:(0)

👋 大家好,我是 阿问学长!专注于分享优质开源项目解析、毕业设计项目指导支持、幼小初高教辅资料推荐等,欢迎关注交流!🚀

缓存注解与使用

前言

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缓存注解的使用,包括:

  1. @Cacheable注解:缓存方法返回值,支持条件缓存和同步加载
  2. @CacheEvict注解:清除缓存数据,支持单个清除和批量清除
  3. @CachePut注解:更新缓存数据,确保缓存与数据库同步
  4. 自定义Key生成策略:灵活的缓存Key生成机制
  5. 缓存配置优化:不同类型数据的差异化缓存策略

通过合理使用缓存注解,可以显著提升应用性能,减少数据库访问压力。

在下一篇文章中,我们将探讨Redis分布式锁与限流的实现。

参考资料


网站公告

今日签到

点亮在社区的每一天
去签到