RuoYi-Vue-Plus (SpringCache、CacheManager、@Cacheable、缓存雪崩、击穿、穿透、CacheUtils 缓存工具类)

一、概述

        1、SpringCache是Spring提供的一个缓存框架,在Spring3.1版本开始支持将缓存添加到现有的spring应用程序中,在4.1开始,缓存已支持JSR-107注释和更多自定义的选项。

        2、SpringCache利用了AOP,实现了基于注解的缓存功能,只需要简单地加一个注解,就能实现缓存功能了,做到了对代码侵入性做小。

        3、SpringCache框架还提供了CacheManager接口,可以实现降低对各种缓存框架的耦合。它不是具体的缓存实现,它只提供一整套的接口和代码规范、配置、注解等,用于整合各种缓存方案,比如Caffeine、Guava Cache、Ehcache。

二、SpringCache概念

接口:


1、Cache接口:缓存接口,定义缓存操作。实现有 如RedisCache、EhCacheCache、ConcurrentMapCache等

2、cacheResolver:指定获取解析器

3、CacheManager:缓存管理器,管理各种缓存(Cache)组件;如:RedisCacheManager,使用redis作为缓存。指定缓存管理器

注解:

1- @Cacheable:在方法执行前查看是否有缓存对应的数据,如果有直接返回数据,如果没有调用方法获取数据返回,并缓存起来。

2- @CacheEvict:将一条或多条数据从缓存中删除。

3- @CachePut:将方法的返回值放到缓存中

4- @EnableCaching:开启缓存注解功能

5- @Caching:组合多个缓存注解;

6- @CacheConfig:统一配置@Cacheable中的value值

三、spring缓存整合redis 

RedisConfig 
类路径: com.ruoyi.framework.config.RedisConfig
1- spring 自动管理缓存机制
@EnableCaching //开启spring缓存,提升性能
@Slf4j
@Configuration
@EnableCaching //1- spring 自动管理缓存机制 ,,提升性能
@EnableConfigurationProperties(RedissonProperties.class)
public class RedisConfig {
 2- 整合自定义缓存管理器

redis配置类,并整合自定义配置

@Slf4j
@Configuration
@EnableCaching //1- spring 自动管理缓存机制 ,,提升性能
@EnableConfigurationProperties(RedissonProperties.class)
public class RedisConfig {

    @Autowired
    private RedissonProperties redissonProperties;

    @Autowired
    private ObjectMapper objectMapper;

    @Bean
    public RedissonAutoConfigurationCustomizer redissonCustomizer() {
        return config -> {
            config.setThreads(redissonProperties.getThreads())
                .setNettyThreads(redissonProperties.getNettyThreads())
                .setCodec(new JsonJacksonCodec(objectMapper));
            RedissonProperties.SingleServerConfig singleServerConfig = redissonProperties.getSingleServerConfig();
            if (ObjectUtil.isNotNull(singleServerConfig)) {
                // 使用单机模式
                config.useSingleServer()
                    //设置redis key前缀
                    .setNameMapper(new KeyPrefixHandler(redissonProperties.getKeyPrefix()))
                    .setTimeout(singleServerConfig.getTimeout())
                    .setClientName(singleServerConfig.getClientName())
                    .setIdleConnectionTimeout(singleServerConfig.getIdleConnectionTimeout())
                    .setSubscriptionConnectionPoolSize(singleServerConfig.getSubscriptionConnectionPoolSize())
                    .setConnectionMinimumIdleSize(singleServerConfig.getConnectionMinimumIdleSize())
                    .setConnectionPoolSize(singleServerConfig.getConnectionPoolSize());
            }
            // 集群配置方式 参考下方注释
            RedissonProperties.ClusterServersConfig clusterServersConfig = redissonProperties.getClusterServersConfig();
            if (ObjectUtil.isNotNull(clusterServersConfig)) {
                config.useClusterServers()
                    //设置redis key前缀
                    .setNameMapper(new KeyPrefixHandler(redissonProperties.getKeyPrefix()))
                    .setTimeout(clusterServersConfig.getTimeout())
                    .setClientName(clusterServersConfig.getClientName())
                    .setIdleConnectionTimeout(clusterServersConfig.getIdleConnectionTimeout())
                    .setSubscriptionConnectionPoolSize(clusterServersConfig.getSubscriptionConnectionPoolSize())
                    .setMasterConnectionMinimumIdleSize(clusterServersConfig.getMasterConnectionMinimumIdleSize())
                    .setMasterConnectionPoolSize(clusterServersConfig.getMasterConnectionPoolSize())
                    .setSlaveConnectionMinimumIdleSize(clusterServersConfig.getSlaveConnectionMinimumIdleSize())
                    .setSlaveConnectionPoolSize(clusterServersConfig.getSlaveConnectionPoolSize())
                    .setReadMode(clusterServersConfig.getReadMode())
                    .setSubscriptionMode(clusterServersConfig.getSubscriptionMode());
            }
            log.info("初始化 redis 配置");
         };
    }

    /**
     * 2-自定义缓存管理器 整合spring-cache
     */
    @Bean
    public CacheManager cacheManager() {
        return new PlusSpringCacheManager();
    }

自定义 管理器PlusSpringCacheManager,实现CacheManager 接口,基于redssion操作缓存

重写设置缓存,设置缓存方法并使用redis操作


/**
 * A {@link org.springframework.cache.CacheManager} implementation
 * backed by Redisson instance.
 * <p>
 * 修改 RedissonSpringCacheManager 源码
 * 重写 cacheName 处理方法 支持多参数
 *
 * @author Nikita Koksharov
 *
 */
@SuppressWarnings("unchecked")
public class PlusSpringCacheManager implements CacheManager {

    //是否自动配置name
    private boolean dynamic = true;

    //是否允许null
    private boolean allowNullValues = true;

    //事务提交之后执行
    private boolean transactionAware = true;

    // 常用缓存配置 ttl;  maxIdleTime; maxSize; 等
    Map<String, CacheConfig> configMap = new ConcurrentHashMap<>();
    // 缓存实例
    ConcurrentMap<String, Cache> instanceMap = new ConcurrentHashMap<>();

    /**
     * Creates CacheManager supplied by Redisson instance
     */
    public PlusSpringCacheManager() {
    }


    /**
     * Defines possibility of storing {@code null} values.
     * <p>
     * Default is <code>true</code>
     *
     * @param allowNullValues stores if <code>true</code>
     */
    public void setAllowNullValues(boolean allowNullValues) {
        this.allowNullValues = allowNullValues;
    }

    /**
     * Defines if cache aware of Spring-managed transactions.
     * If {@code true} put/evict operations are executed only for successful transaction in after-commit phase.
     * <p>
     * Default is <code>false</code>
     *
     * @param transactionAware cache is transaction aware if <code>true</code>
     */
    public void setTransactionAware(boolean transactionAware) {
        this.transactionAware = transactionAware;
    }

    /**
     * Defines 'fixed' cache names.
     * A new cache instance will not be created in dynamic for non-defined names.
     * <p>
     * `null` parameter setups dynamic mode
     *
     * @param names of caches
     */
    public void setCacheNames(Collection<String> names) {
        if (names != null) {
            for (String name : names) {
                getCache(name);
            }
            dynamic = false;
        } else {
            dynamic = true;
        }
    }

    /**
     * Set cache config mapped by cache name
     *
     * @param config object
     */
    public void setConfig(Map<String, ? extends CacheConfig> config) {
        this.configMap = (Map<String, CacheConfig>) config;
    }

    protected CacheConfig createDefaultConfig() {
        return new CacheConfig();
    }

    @Override
    public Cache getCache(String name) {
        // 重写 cacheName 支持多参数
        /**
         * 演示案例   :  String DEMO_CACHE = "demo:cache#60s#10m#20";
         */
        String[] array = StringUtils.delimitedListToStringArray(name, "#");
        name = array[0];

        Cache cache = instanceMap.get(name);
        if (cache != null) {
            return cache;
        }
        //2- dynamic=false 不会动态生成
        if (!dynamic) {
            //return cache;
            return null;
        }

        CacheConfig config = configMap.get(name);
        if (config == null) {
            config = createDefaultConfig();
            configMap.put(name, config);
        }

        //setTTL
        if (array.length > 1) {
            config.setTTL(DurationStyle.detectAndParse(array[1]).toMillis());
        }
        //setMaxIdleTime
        if (array.length > 2) {
            config.setMaxIdleTime(DurationStyle.detectAndParse(array[2]).toMillis());
        }
        //setMaxSize
        if (array.length > 3) {
            config.setMaxSize(Integer.parseInt(array[3]));
        }

        if (config.getMaxIdleTime() == 0 && config.getTTL() == 0 && config.getMaxSize() == 0) {
            return createMap(name, config);
        }

        return createMapCache(name, config);
    }

    private Cache createMap(String name, CacheConfig config) {
        //1-获取缓存
        RMap<Object, Object> map = RedisUtils.getClient().getMap(name);
        //2-没有过期时间传2个参数
        Cache cache = new RedissonCache(map, allowNullValues);
        // 3-事务提交 之后执行
        if (transactionAware) {
            cache = new TransactionAwareCacheDecorator(cache);
        }
        //4-不存在就添加
        Cache oldCache = instanceMap.putIfAbsent(name, cache);
        if (oldCache != null) {
            cache = oldCache;
        }
        return cache;
    }

    private Cache createMapCache(String name, CacheConfig config) {
        //1-获取缓存
        RMapCache<Object, Object> map = RedisUtils.getClient().getMapCache(name);
        //2-有过期时间传3个参数 ,config 里面有  ttl、maxIdleTime、maxSize
        Cache cache = new RedissonCache(map, config, allowNullValues);
        // 3-事务提交 之后执行
        if (transactionAware) {
            cache = new TransactionAwareCacheDecorator(cache);
        }
        //4-不存在就添加
        Cache oldCache = instanceMap.putIfAbsent(name, cache);
        if (oldCache != null) {
            cache = oldCache;
        } else {
            map.setMaxSize(config.getMaxSize());
        }
        return cache;
    }

   //返回不可修改的集合
    @Override
    public Collection<String> getCacheNames() {
        return Collections.unmodifiableSet(configMap.keySet());
    }


}
3-@Cacheable 整合redis 之后的使用

以下Cacheable几个属性分别演示了如何保存缓存:(支持SPEL表达式

  • cacheNames 
  • key 
  • sync
  • condition 
  • sync 
/**
     * <简述>cacheNames: 指定名称 可以是数组
     *        key: 支持spel表达式,可以获取参数
     * @author syf
     * @date 2024/5/7 11:03
     * @param id
     * @param pageQuery
     * @return java.lang.String
     */
    @Cacheable(cacheNames = "cache1", key = "#id + '_cache' + #pageQuery.pageNum")
    @GetMapping("test1")
    public  String test1(String id, PageQuery pageQuery){

        return "ok";
    }

    /**
     * <简述> condition :符合条件进行缓存
     *        #id != null :表示传入 id不为空才会缓存进入redis,id为空则不缓存
     * @author syf
     * @date 2024/5/7 11:03
     * @param id
     * @return java.lang.String
     */
    @Cacheable(cacheNames = "cache2", key = "#id + '_cache'" , condition = "#id != null")
    @GetMapping("test2")
    public  String test2(String id){

        return "ok";
    }

    /**
     * <简述> unless 符合条件不缓存
     *        #result == null :接口返回结果为空则不进行缓存
     * @author syf
     * @date 2024/5/7 11:03
     * @param id
     * @return java.lang.String
     */
    @Cacheable(cacheNames = "cache3", key = "#id + '_cache'" , unless = "#result == null")
    @GetMapping("test3")
    public  String test3(String id){
        return null;
    }

    /**
     * <简述> sync = true
     *       同步阻塞:同时进来多个请求, 等待前面调用返回并缓存,才能回进入下个请求
     *       作用:防止缓存积存
     * @author syf
     * @date 2024/5/7 11:03
     * @param id
     * @return java.lang.String
     */
    @Cacheable(cacheNames = "cache4", key = "#id + '_cache'", sync = true)
    @GetMapping("test4")
    public  String test4(String id){
        return null;
    }

    /**
     * <简述> 获取类中参数
     *         比较繁琐,一般是在实现类中传递登录参数,用spel获取
     * @author syf
     * @date 2024/5/7 11:03
     * @return java.lang.String
     */
    @Cacheable(cacheNames = "cache5", key = "T(com.ruoyi.common.helper.LoginHelper).getLoginUser().getLoginId()")
    @GetMapping("test5")
    public  String test5(){
        LoginUser loginUser = LoginHelper.getLoginUser();
        return "ok";
    }
4- @CachePut

缓存更新

执行该方法,并将执行结果以键值对的形式存入指定的缓存中。

  /**
     * <简述> 结果不为空进行更新
     * @author syf
     * @date 2024/5/7 11:03
     * @param id
     * @return java.lang.String
     */
    @CachePut(cacheNames = "cache2", key = "#id + '_cache'" , condition = "#result != null")
    @GetMapping("test2")
    public  String test2(String id){
        boolean flag = doUpdate();
        return flag  ? "ok" : null;
    }
5- @CacheEvict

缓存删除

执行该方法,并将缓存中结果删除。

allEntries  删除所有cacheNames = "cache4",下面缓存
beforeInvocation  默认false,方法执行之后有异常不执行。true:方法执行之后有异常,也执行
  /**
     * <简述> 删除缓存
     * @author syf
     * @date 2024/5/7 11:03
     * @param id
     * @return java.lang.String
     */
    @CacheEvict(cacheNames = "cache4", key = "#id + '_cache'")
    @GetMapping("test7")
    public  String test7(String id){
        boolean flag = doDelete();
        return flag  ? "ok" : null;
    }


  /**
     * <简述> 删除所有缓存
     * @author syf
     * @date 2024/5/7 11:03
     * @param id
     * @return java.lang.String
     */
    @CacheEvict(cacheNames = "cache4", allEntries = true)
    @GetMapping("test8")
    public  String test8(String id){
        return null;
    }

   /**
     * <简述> beforeInvocation 无论是否有异常都执行操作
     * @author syf
     * @date 2024/5/7 11:03
     * @param id
     * @return java.lang.String
     */
    @CacheEvict(cacheNames = "cache4", beforeInvocation = true)
    @GetMapping("test9")
    public  String test9(String id){
        return null;
    }
 6-@Caching:

指定多个Spring Cache相关的注解

三个属性:cacheable、put和evict,分别用于指定@Cacheable、@CachePut和@CacheEvict。

    @Caching(
            cacheable = {@Cacheable(value = "uer1",key = "#userName")},
            put = {@CachePut(value = "uer1", key = "#result.id"),
                    @CachePut(value = "uer1", key = "#result.age")
            }
    )
    public User getStuByStr(String userName) {
      
        List<User> users= listMapper.selectByList(studentExample);
        return Optional.ofNullable(users).orElse(null).get(0);
    }

四、若依框架中缓存使用(自定义SpringCache 源码解读)、

CacheNames 缓存名称配置类:
类位置:com.ruoyi.common.constant.CacheNames
key 格式为: cacheNames#ttl#maxIdleTime#maxSize
/**
 * 缓存组名称常量
 * <p>
 * key 格式为 cacheNames#ttl#maxIdleTime#maxSize
 * <p>
 * ttl 过期时间 如果设置为0则不过期 默认为0
 * maxIdleTime 最大空闲时间 根据LRU算法清理空闲数据 如果设置为0则不检测 默认为0   (超过maxIdleTime LRU算法自动清理)
 * maxSize 组最大长度 根据LRU算法清理溢出数据 如果设置为0则无限长 默认为0
 * <p>
 * 例子: test#60s、test#0#60s、test#0#1m#1000、test#1h#0#500
 */

PlusSpringCacheManager 实现 CacheManager 接口,重写 getCache 方法,

就是配置了  :ttl、maxIdleTime、maxSize  三个参数吗,如下:

@Override
    public Cache getCache(String name) {
        // 重写 cacheName 支持多参数
        /**
         * 1-演示案例   :  String DEMO_CACHE = "demo:cache#60s#10m#20";
         */
        String[] array = StringUtils.delimitedListToStringArray(name, "#");
        name = array[0];

        Cache cache = instanceMap.get(name);
        if (cache != null) {
            return cache;
        }
        //2- dynamic=false 不会动态生成
        if (!dynamic) {
            //return cache;
            return null;
        }

        CacheConfig config = configMap.get(name);
        if (config == null) {
            config = createDefaultConfig();
            configMap.put(name, config);
        }
        
        //setTTL
        if (array.length > 1) {
            config.setTTL(DurationStyle.detectAndParse(array[1]).toMillis());
        }
        //setMaxIdleTime
        if (array.length > 2) {
            config.setMaxIdleTime(DurationStyle.detectAndParse(array[2]).toMillis());
        }
        //setMaxSize
        if (array.length > 3) {
            config.setMaxSize(Integer.parseInt(array[3]));
        }

        if (config.getMaxIdleTime() == 0 && config.getTTL() == 0 && config.getMaxSize() == 0) {
            return createMap(name, config);
        }

        return createMapCache(name, config);
    }

 重点:下面就是PlusSpringCacheManager ,操作缓存的地方

上面调用了:createMap、createMapCache 2个方法对比:

1-逻辑:

createMapCache 多了个 setMaxSize判断,其他都一样

else {
            map.setMaxSize(config.getMaxSize());
        }

2- 返回类型 

createMap 返回  RMap

createMapCache 返回 RMapCache

对比: RMapCache 继承了 RMap 多了对于ttl、maxIdleTime、maxSize 的配置

相同 :都是基于redisson,缓存到redis

private Cache createMap(String name, CacheConfig config) {
        //1-获取缓存
        RMap<Object, Object> map = RedisUtils.getClient().getMap(name);
        //2-没有过期时间传2个参数
        Cache cache = new RedissonCache(map, allowNullValues);
        // 3-事务提交 之后执行
        if (transactionAware) {
            cache = new TransactionAwareCacheDecorator(cache);
        }
        //4-不存在就添加
        Cache oldCache = instanceMap.putIfAbsent(name, cache);
        if (oldCache != null) {
            cache = oldCache;
        }
        return cache;
    }

    private Cache createMapCache(String name, CacheConfig config) {
        //1-获取缓存
        RMapCache<Object, Object> map = RedisUtils.getClient().getMapCache(name);
        //2-有过期时间传3个参数 ,config 里面有  ttl、maxIdleTime、maxSize
        Cache cache = new RedissonCache(map, config, allowNullValues);
        // 3-事务提交 之后执行
        if (transactionAware) {
            cache = new TransactionAwareCacheDecorator(cache);
        }
        //4-不存在就添加
        Cache oldCache = instanceMap.putIfAbsent(name, cache);
        if (oldCache != null) {
            cache = oldCache;
        } else {
            map.setMaxSize(config.getMaxSize());
        }
        return cache;
    }

 上面  TransactionAwareCacheDecorator:

所执行的put操作,是在事务提交之后执行

 public void put(final Object key, @Nullable final Object value) {
        if (TransactionSynchronizationManager.isSynchronizationActive()) {
            TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
                public void afterCommit() {
                    TransactionAwareCacheDecorator.this.targetCache.put(key, value);
                }
            });
        } else {
            this.targetCache.put(key, value);
        }

    }

 五、缓存工具类

private static final CacheManager CACHE_MANAGER = SpringUtils.getBean(CacheManager.class);

主要是获取 CacheManager 接口,提供对缓存CRUD操作 :
public interface CacheManager {
    @Nullable
    Cache getCache(String name);

    Collection<String> getCacheNames();
}
 
package com.ruoyi.common.utils.redis;

import com.ruoyi.common.utils.spring.SpringUtils;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.redisson.api.RMap;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;

import java.util.Set;

/**
 * 缓存操作工具类 {@link }
 *
 * @author Michelle.Chung
 * @date 2022/8/13
 */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@SuppressWarnings(value = {"unchecked"})
public class CacheUtils {

    private static final CacheManager CACHE_MANAGER = SpringUtils.getBean(CacheManager.class);

    /**
     * 获取缓存组内所有的KEY
     *
     * @param cacheNames 缓存组名称
     */
    public static Set<Object> keys(String cacheNames) {
        RMap<Object, Object> rmap = (RMap<Object, Object>) CACHE_MANAGER.getCache(cacheNames).getNativeCache();
        return rmap.keySet();
    }

    /**
     * 获取缓存值
     *
     * @param cacheNames 缓存组名称
     * @param key        缓存key
     */
    public static <T> T get(String cacheNames, Object key) {
        Cache.ValueWrapper wrapper = CACHE_MANAGER.getCache(cacheNames).get(key);
        return wrapper != null ? (T) wrapper.get() : null;
    }

    /**
     * 保存缓存值
     *
     * @param cacheNames 缓存组名称
     * @param key        缓存key
     * @param value      缓存值
     */
    public static void put(String cacheNames, Object key, Object value) {
        CACHE_MANAGER.getCache(cacheNames).put(key, value);
    }

    /**
     * 删除缓存值
     *
     * @param cacheNames 缓存组名称
     * @param key        缓存key
     */
    public static void evict(String cacheNames, Object key) {
        CACHE_MANAGER.getCache(cacheNames).evict(key);
    }

    /**
     * 清空缓存值
     *
     * @param cacheNames 缓存组名称
     */
    public static void clear(String cacheNames) {
        CACHE_MANAGER.getCache(cacheNames).clear();
    }

}

六、缓存雪崩

概念:

        缓存集中过期失效(大量key失效)。所有请求直接查询数据库了,而对数据库造成巨大压力,严重可能的会导致数据库宕机。从而形成一系列连锁反应,造成整个系统崩溃。

解决:

  • 1、实现Redis的高可用、改为主从+哨兵集群模式
  • 2、允许的话,也可以设置热点数据不过期(或者不同业务设置不同过期时间 例子: test#60s、test#0#60s、test#0#1m#1000、test#1h#0#500)
  • 3、开启Redis的RDB+AOF组合持久化策略,以便快速恢复

七、缓存击穿 

概念:

缓存击穿指的是热点key在某个特殊的场景时间内恰好失效了,恰好有大量并发请求过来了,导致大量的请求都打到数据库上,造成数据库极大的压力,这就是缓存击穿问题。

对比缓存雪崩:        

        雪崩大量key失效,击穿 某几个热点key失效

 解决:

互斥锁方案,保证同一时间只有一个业务线程去数据库获取数据填充到Redis中,更新缓存,未能获取互斥锁的请求,需要等待锁释放后重新读取缓存。获取成功直接返回结果获取失败则再次尝试获取锁,重复上述流程

若依框架中的实现:简单举例

        sync = true  同步阻塞:同时进来多个请求, 等待前面调用返回并缓存,才能回进入下个请求
  

   /**
     * <简述> sync = true
     *       同步阻塞:同时进来多个请求, 等待前面调用返回并缓存,才能回进入下个请求
     *   
     * @author syf
     * @date 2024/5/7 11:03
     * @param id
     * @return java.lang.String
     */
    @Cacheable(cacheNames = "cache4", key = "#id + '_cache'", sync = true)
    @GetMapping("test4")
    public  String test4(String id){
        return null;
    }

四、缓存穿透

概念:

用户在不断访问一个在缓存和数据库中都没有的数据,缓存无法命中,从而导致一直请求数据库,流量过大就会导致数据库的崩溃,这就是缓存穿透问题。

 解决:

  • 接口层增加校验,如用户鉴权等;
  • 使用布隆过滤器快速判断数据是否存在,避免通过查询数据库来判断数据是否存在
  • 将空结果(NULL)或默认查询结果存入到缓存中,并设置值过期时间。

布隆过滤器可以参考文章:

redis中布隆过滤器使用详解_redis布隆过滤器使用-优快云博客https://blog.youkuaiyun.com/w1014074794/article/details/129750865

 博主精心整理专栏,CV大法即可用,感谢您小手点一点 手动跪拜:  

1- SpringBoot框架常用配置(若依),代码解读:

https://blog.youkuaiyun.com/javaxueba/category_12611571.html?fromshare=blogcolumn&sharetype=blogcolumn&sharerId=12611571&sharerefer=PC&sharesource=javaxueba&sharefrom=from_link

2- java常用工具类整理,示例演示:

https://blog.youkuaiyun.com/javaxueba/category_12752969.html?fromshare=blogcolumn&sharetype=blogcolumn&sharerId=12752969&sharerefer=PC&sharesource=javaxueba&sharefrom=from_link

3- CompletableFuture 异步编排实际代码展示

https://blog.youkuaiyun.com/javaxueba/category_12711097.html?fromshare=blogcolumn&sharetype=blogcolumn&sharerId=12711097&sharerefer=PC&sharesource=javaxueba&sharefrom=from_link

4- XXL-JOB 详细学习,手把手带入门

https://blog.youkuaiyun.com/javaxueba/category_12750420.html?fromshare=blogcolumn&sharetype=blogcolumn&sharerId=12750420&sharerefer=PC&sharesource=javaxueba&sharefrom=from_link

<think> 我们正在解决Ruoyi-Vue-Plus中使用@Cacheable注解后数据一直不变的问题。 根据引用[4]的内容,我们知道RuoYi-Vue-Plus中使用了Spring Cache机制,并且整合了Redis作为缓存。 数据一直不变的原因可能是缓存没有更新,当我们更新了数据库的数据,但是缓存中的数据没有被清除或更新,导致每次查询都返回旧的缓存数据。 解决思路: 1. 检查是否在更新数据的方法上添加了缓存清除或更新的注解(如@CacheEvict或@CachePut)。 2. 检查缓存配置,特别是缓存的key和缓存的有效期。 具体步骤: 根据引用[4]中提到的,RuoYi-Vue-Plus使用了@CacheConfig、@Cacheable等注解,并且配置了Redis缓存管理器。 可能的原因及解决办法: 原因1:在更新数据的方法上没有使用@CacheEvict或@CachePut来清除或更新缓存。 解决办法:在更新数据的方法上添加@CacheEvict注解,清除对应的缓存,这样下次查询时就会重新从数据库读取并缓存。 原因2:缓存key不一致。确保在更新时清除的缓存key和查询时使用的缓存key一致。 解决办法:检查@Cacheable和@CacheEvict中的key属性是否一致。可以使用相同的key生成策略。 原因3:缓存过期时间设置过长,或者根本没有过期,导致缓存一直有效。 解决办法:检查缓存配置,看是否设置了合理的过期时间。可以在RedisConfig中配置缓存过期时间。 原因4:可能缓存到了不同的缓存管理器,或者缓存名称不一致。 解决办法:检查@Cacheable注解中的cacheNames/value是否与@CacheEvict中的一致。 根据引用[4]中提到的RedisConfig配置类,我们可以查看是否配置了缓存过期时间。如果没有配置,可以添加。 示例代码: 假设我们有一个查询用户的方法: ```java @Cacheable(value = "userCache", key = "#userId") public User getUserById(Long userId) { // 从数据库查询 } ``` 更新用户的方法: ```java @CacheEvict(value = "userCache", key = "#user.userId") public void updateUser(User user) { // 更新数据库 } ``` 另外,在RedisConfig中,我们可以配置缓存的默认过期时间。例如: ```java @Bean public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) { RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig() .entryTtl(Duration.ofMinutes(10)) // 设置默认过期时间10分钟 .disableCachingNullValues(); // 不缓存null值 return RedisCacheManager.builder(redisConnectionFactory) .cacheDefaults(config) .build(); } ``` 如果问题仍然存在,可以检查Redis服务器是否正常运行,以及是否有网络问题导致缓存清除失败。 此外,引用[4]中提到了“整合自定义缓存管理器”,我们可以检查项目中是否确实配置了缓存管理器,并且配置正确。 总结: 1. 确保在更新数据时清除缓存(使用@CacheEvict)。 2. 确保缓存key一致。 3. 配置合理的缓存过期时间。 4. 检查缓存管理器的配置。 如果按照以上步骤操作,应该可以解决缓存数据不变的问题。 注意:在RuoYi-Vue-Plus中,可能已经有一些默认配置,我们可以参考项目中的RedisConfig类(路径:com.ruoyi.framework.config.RedisConfig)来确认配置。 如果问题复杂,可以查看项目文档或社区支持。 根据引用[5],项目地址是:https://gitcode.com/dromara/RuoYi-Vue-Plus,可以在项目中查找相关配置和使用示例。 </think>在 RuoYi-Vue-Plus 中使用 `@Cacheable` 注解后数据不更新的问题,通常由以下原因及解决方案导致: ### 🔍 主要原因与解决方案 1. **缓存未清除** - **问题**:数据更新后未清除旧缓存,导致始终返回缓存结果 - **解决**:在数据更新方法添加 `@CacheEvict` 注解清除缓存 ```java @CacheEvict(value = "yourCacheName", key = "#id") // 清除指定key的缓存 public void updateData(DataEntity entity) { // 更新数据库操作 } ``` 2. **缓存Key不匹配** - **问题**:`@Cacheable` 和 `@CacheEvict` 的key生成规则不一致 - **解决**:统一使用相同的SpEL表达式生成key ```java // 查询方法 @Cacheable(value = "dataCache", key = "#type + '_' + #id") public DataEntity getData(String type, Long id) { ... } // 更新方法 @CacheEvict(value = "dataCache", key = "#entity.type + '_' + #entity.id") ``` 3. **缓存配置问题** - **问题**:未正确配置缓存管理器(根据引用[4]) - **解决**:检查 `RedisConfig` 配置类: ```java @Configuration @EnableCaching // 确保开启缓存 public class RedisConfig { @Bean public RedisCacheManager cacheManager(RedisConnectionFactory factory) { RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig() .entryTtl(Duration.ofMinutes(30)); // 设置缓存过期时间 return RedisCacheManager.builder(factory).cacheDefaults(config).build(); } } ``` 4. **事务传播问题** - **问题**:缓存操作与数据库事务不同步 - **解决**:在更新方法添加事务注解: ```java @Transactional @CacheEvict(value = "dataCache", key = "#id") public void update(Long id) { ... } ``` ### ⚙️ 调试步骤 1. 检查 Redis 中是否存在旧缓存: ```bash redis-cli keys '*yourCacheName*' ``` 2. 在 `application.yml` 开启调试日志: ```yaml logging: level: org.springframework.cache: TRACE ``` 3. 确认方法调用是否命中缓存(查看日志中的 `Cache hit`/`Cache miss` 记录) ### 💡 最佳实践建议 1. **多租户场景**(引用[3]):在缓存key中加入租户ID ```java @Cacheable(value = "userData", key = "#tenantId + '_' + #userId") ``` 2. **批量清除**:使用 `@CacheEvict(allEntries = true)` 清空整个缓存区域 3. **缓存更新策略**:复杂场景改用 `@CachePut` 注解同步更新缓存值 > 提示:RuoYi-Vue-Plus 默认集成 Redis 作为缓存后端(引用[4]),确保 Redis 服务正常运行且配置正确(`application.yml` 中的 `spring.redis` 配置项)。项目地址详见引用[5]。 --- ### 🔎 相关问题 1. RuoYi-Vue-Plus 中如何实现多级缓存策略? 2. `@Cacheable` 与 `@CachePut` 注解的使用场景有什么区别? 3. 如何在分布式环境下保证缓存与数据库的一致性? 4. RuoYi 的数据权限控制(引用[3])如何与缓存机制协同工作? 5. 如何监控和优化 RuoYi-Vue-Plus缓存命中率? [^1]: RuoYi-Vue-Plus 架构设计深度解析 [^2]: 分布式架构解决方案对比表 [^3]: 数据范围权限类型定义 [^4]: Spring 缓存整合 Redis 配置 [^5]: 项目源码地址与功能说明
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

syfjava

请博主喝杯蜜雪冰城

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值