Redis总结 -- springboot2.0整合spring cache + redis

文章目录

  • 一,spring cache
    • 简介
  • 二,springboot2.0整合spring cache + redis手动配置
    • 1.新建redis配置类RedisConfig
      • 继承 CachingConfigurerSupport类完成对redis的基本配置
        • CacheErrorHandler
        • KeyGenerator
        • cacheManager
    • 2.使用基于注解的缓存
    • 3.注意问题
      • @Cacheable注解不生效问题

一,spring cache

简介

注解驱动的缓存

二,springboot2.0整合spring cache + redis手动配置

1.新建redis配置类RedisConfig

继承 CachingConfigurerSupport类完成对redis的基本配置

public class CachingConfigurerSupport implements CachingConfigurer {
    public CachingConfigurerSupport() {
    }

    //自定义缓存管理器
    @Nullable
    public CacheManager cacheManager() {
        return null;
    }

    //通过自定义CacheResolver实现动态选择CacheManager
    @Nullable
    public CacheResolver cacheResolver() {
        return null;
    }

    //自定义key生成策略
    @Nullable
    public KeyGenerator keyGenerator() {
        return null;
    }

    //自定义缓存读写异常
    @Nullable
    public CacheErrorHandler errorHandler() {
        return null;
    }
}

CacheErrorHandler

CacheErrorHandler是一个缓存异常处理接口,定义了缓存读写异常的方法

public interface CacheErrorHandler {
    void handleCacheGetError(RuntimeException var1, Cache var2, Object var3);

    void handleCachePutError(RuntimeException var1, Cache var2, Object var3, @Nullable Object var4);

    void handleCacheEvictError(RuntimeException var1, Cache var2, Object var3);

    void handleCacheClearError(RuntimeException var1, Cache var2);
}

缓存仅仅是为了业务更快地查询而存在的,如果因为缓存操作失败导致正常的业务流程失败,有点得不偿失了。因此需要开发者自定义CacheErrorHandler处理缓存读写的异常。

redis缓存读写异常的默认实现是SimpleCacheErrorHandler

通过查看源码可以知道,SimpleCacheErrorHandler对每种错误都是简单的抛出一个Exception

public class SimpleCacheErrorHandler implements CacheErrorHandler {
    public SimpleCacheErrorHandler() {
    }

    public void handleCacheGetError(RuntimeException exception, Cache cache, Object key) {
        throw exception;
    }

    public void handleCachePutError(RuntimeException exception, Cache cache, Object key, @Nullable Object value) {
        throw exception;
    }

    public void handleCacheEvictError(RuntimeException exception, Cache cache, Object key) {
        throw exception;
    }

    public void handleCacheClearError(RuntimeException exception, Cache cache) {
        throw exception;
    }
}

言归正传,自定义缓存异常只需要重写这四个方法即可

    /**
     * 处理缓存读写异常(缓存自定义异常)
     * @return
     */
    @Override
    public CacheErrorHandler errorHandler() {

       CacheErrorHandler cacheErrorHandler = new CacheErrorHandler() {

           @Override
           public void handleCacheGetError(RuntimeException e, Cache cache, Object key) {
                log.error("缓存 查找 失败,失败原因:"+e.getMessage()+"缓存key:"+key);
           }

           @Override
           public void handleCachePutError(RuntimeException e, Cache cache, Object key, Object o1) {
               log.error("缓存 更新 失败,失败原因:"+e.getMessage()+"缓存key:"+key);
           }

           @Override
           public void handleCacheEvictError(RuntimeException e, Cache cache, Object key) {
               log.error("缓存 删除 失败,失败原因:"+e.getMessage()+"缓存key:"+key);
           }

           @Override
           public void handleCacheClearError(RuntimeException e, Cache cache) {
               log.error("缓存 清除 失败,失败原因:"+e.getMessage());
           }
       };
       return cacheErrorHandler;
    }

KeyGenerator

缓存key生成器,定义了缓存key的生成方法

@FunctionalInterface
public interface KeyGenerator {
    Object generate(Object var1, Method var2, Object... var3);
}

spring cache缓存的默认key生成策略是SimpleKeyGenerator

public class SimpleKeyGenerator implements KeyGenerator {
    public SimpleKeyGenerator() {
    }

    public Object generate(Object target, Method method, Object... params) {
        return generateKey(params);
    }

    //生成key
    public static Object generateKey(Object... params) {
        //没有方法参数:key = 0
        if (params.length == 0) {
            return SimpleKey.EMPTY;
        } else {
            //参数个数为1:key = 第一个参数
            if (params.length == 1) {
                Object param = params[0];
                if (param != null && !param.getClass().isArray()) {
                    return param;
                }
            }
            //如果参数多于一个的话则使用所有参数的hashCode作为key。
            return new SimpleKey(params);
        }
    }
}

场景:当我们先调用了getModel1(1),ehcache就会将方法的返回结果以"1"为key放入缓存中,当我们再调用getModel2(1)时,ehcache就会从缓存中找key为"1"的数据(即 Model1 )并试图将它转换为Model2 ,这就出现了异常: Model1 can not be cast to Model2…所以我们需要自定义key策略来解决这个问题,将类名和方法名和参数列表一起来生成key,下面是自定义的Key生成代码:

    /**
     * 自定义key生成器
     *
     * key ---> hashCode( 类名(class)+ 方法名(method)+ 所有参数(params))
     * @return
     */
    @Bean
    @Override
    public KeyGenerator keyGenerator() {
        return (target,method,params)->{
            // 采取拼接的方式生成key
            StringBuilder stringBuilder = new StringBuilder();
            // 目标类的类名
            stringBuilder.append(target.getClass().getName());
            stringBuilder.append(":");
            // 目标方法名
            stringBuilder.append(method.getName());
            // 参数
            for(Object object : params){
                stringBuilder.append(":"+String.valueOf(object));
            }
            String result = String.valueOf(stringBuilder.hashCode());
            log.info("自定义的key为:"+result);
            return result;
        };
    }

cacheManager

如果需要Spring缓存可以正常工作,必须配置一个CacheManager。

Redis总结 -- springboot2.0整合spring cache + redis_第1张图片
CacheManager简单描述就是用来存放Cache,Cache用于存放具体的key-value值

在我们没有配置cacheConfiguration时,默认使用的是SimpleCacheConfiguration,其缓存管理器是ConcurrentMapCacheManager

 @Bean
    public ConcurrentMapCacheManager cacheManager() {
        ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager();
        List<String> cacheNames = this.cacheProperties.getCacheNames();
        if (!cacheNames.isEmpty()) {
            cacheManager.setCacheNames(cacheNames);
        }

        return (ConcurrentMapCacheManager)this.customizerInvoker.customize(cacheManager);
    }

ConcurrentMapCacheManager通过一个ConcurrentMap作为缓存容器存放缓存

ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap(16);

查询缓存cache时根据cacheName去取缓存

    @Nullable
    public Cache getCache(String name) {
        Cache cache = (Cache)this.cacheMap.get(name);
        if (cache == null && this.dynamic) {
            ConcurrentMap var3 = this.cacheMap;
            synchronized(this.cacheMap) {
                cache = (Cache)this.cacheMap.get(name);
                if (cache == null) {
                    cache = this.createConcurrentMapCache(name);
                    this.cacheMap.put(name, cache);
                }
            }
        }

        return cache;
    }

所以我们定义一个RedisCacheManager目的就是为了操作redisCache(cache接口实现类)

    /**
     * 自定义缓存管理器
     * @param factory   自动注入spring容器中的jedisConnectionFactory
     * @return
     */
    @Bean
    public CacheManager cacheManager(JedisConnectionFactory factory) {
        RedisSerializer<String> redisSerializer = new StringRedisSerializer();
        FastJsonRedisSerializer fastJsonRedisSerializer = new FastJsonRedisSerializer(Object.class);

        // 配置序列化(解决乱码的问题)
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                // 7 天缓存过期
                .entryTtl(Duration.ofDays(7))
                // key序列化
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
                // 值序列化
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(fastJsonRedisSerializer))
                // 不缓存空值
                .disableCachingNullValues();
        // 通过连接工厂构建缓存管理器
        RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
                // 注入配置
                .cacheDefaults(config)
                .build();
        return cacheManager;
    }

2.使用基于注解的缓存

缓存注解功能不止如此,需要时网上查阅

@Service
public class SpringCacheServiceImpl implements SpringCacheService{

    /**
     * 新增缓存
     * @param age
     * @return
     */
    @Override
    @Cacheable(key = "#result.name", cacheNames = "user")
    public User insertUser(int age) {

        User user = new User();
        user.setAge(age);
        user.setName("kevin123");
        System.out.println("adwada");
        return user;
    }

    /**
     * 更新缓存
     * @param user
     * @return
     */
    @CachePut(key = "#user.age",cacheNames = "user")
    @Override
    public User updateUser(User user) {

        user.setName("kevin12453");
        System.out.println("adwada");
        return user;
    }

    /**
     * 删除缓存
     * @param age
     * @return
     */
    @CacheEvict(key = "#p0",cacheNames = "user")
    @Override
    public int deleteUser(int age) {
        System.out.println("删除成功");
        return 0;
    }


    /**
     * 多种缓存规则
     * @param age
     * @return
     */
    @Caching(cacheable = {
            @Cacheable(key = "#age",cacheNames = "user")
    },put = {
            @CachePut(key = "#result.name",cacheNames = "user")
    })
    @Override
    public User selectUser(int age) {

        User user = new User();
        user.setAge(age);
        user.setName("kevin123233");
        System.out.println("adwadawdawdawa");
        return user;
    }
}

缓存结果:

Redis总结 -- springboot2.0整合spring cache + redis_第2张图片

Redis总结 -- springboot2.0整合spring cache + redis_第3张图片

3.注意问题

@Cacheable注解不生效问题

@Cacheable注解中:一个方法A调同一个类里的另一个有缓存注解的方法B,这样是不走缓存的。例如在同一个service里面两个方法的调用,缓存是不生效的

为什么缓存没有被正常创建??

因为@Cacheable 是使用AOP 代理实现的 ,通过创建内部类来代理缓存方法,这样就会导致一个问题,类内部的方法调用类内部的缓存方法不会走代理,不会走代理,就不能正常创建缓存,所以每次都需要去调用数据库。

你可能感兴趣的:(spring,boot,Redis,Redis总结)