设为首页 收藏本站
查看: 1536|回复: 0

[经验分享] Spring Boot 使用Redis缓存

[复制链接]

尚未签到

发表于 2017-12-20 20:48:28 | 显示全部楼层 |阅读模式
  本文示例源码,请看这里
  续接上文:Spring Boot 1.5.4集成Redis
  Spring Cache的官方文档,请看这里

缓存存储
  Spring 提供了很多缓存管理器,例如:


  • SimpleCacheManager
  • EhCacheCacheManager
  • CaffeineCacheManager
  • GuavaCacheManager
  • CompositeCacheManager  
    这里我们要用的是除了核心的Spring框架之外,Spring Data提供的缓存管理器:RedisCacheManager

  在Spring Boot中通过@EnableCaching注解自动化配置合适的缓存管理器(CacheManager),默认情况下Spring Boot根据下面的顺序自动检测缓存提供者:


  • Generic
  • JCache (JSR-107)
  • EhCache 2.x
  • Hazelcast
  • Infinispan
  • Redis
  • Guava
  • Simple
  但是因为我们之前已经配置了redisTemplate了,Spring Boot无法就无法自动给RedisCacheManager设置redisTemplate了,所以接下来要自己配置CacheManager 。


  • 首先修改RedisConfig配置类,添加@EnableCaching注解,并继承CachingConfigurerSupport,重写CacheManager 方法
  

...  
@Configuration
  
@EnableCaching

  
public>  

  @Bean
  public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) {
  RedisTemplate<String, String> redisTemplate = new RedisTemplate<String, String>();
  redisTemplate.setConnectionFactory(factory);
  redisTemplate.afterPropertiesSet();
  setSerializer(redisTemplate);
  return redisTemplate;
  }
  

  private void setSerializer(RedisTemplate<String, String> template) {
  Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
  ObjectMapper om = new ObjectMapper();
  om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
  om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
  jackson2JsonRedisSerializer.setObjectMapper(om);
  template.setKeySerializer(new StringRedisSerializer());
  template.setValueSerializer(jackson2JsonRedisSerializer);
  }
  

  
@Bean
  public CacheManager cacheManager(RedisTemplate redisTemplate) {
  RedisCacheManager rcm = new RedisCacheManager(redisTemplate);
  // 设置缓存过期时间,秒
  rcm.setDefaultExpiration(60);
  return rcm;
  }
  
...
  

  Spring提供了如下注解来声明缓存规则:



  • @Cacheable triggers cache population
  • @CacheEvict triggers cache eviction
  • @CachePut updates the cache without interfering with the method execution
  • @Caching regroups multiple cache operations to be applied on a method
    @CacheConfig shares some common cache-related settings at>

注  解
描  述
@Cacheable
表明Spring在调用方法之前,首先应该在缓存中查找方法的返回值。如果这个值能够找到,就会返回缓存的值。否则的话,这个方法就会被调用,返回值会放到缓存之中
@CachePut
表明Spring应该将方法的返回值放到缓存中。在方法的调用前并不会检查缓存,方法始终都会被调用
@CacheEvict
表明Spring应该在缓存中清除一个或多个条目
@Caching
这是一个分组的注解,能够同时应用多个其他的缓存注解
@CacheConfig
可以在类层级配置一些共用的缓存配置  @Cacheable和@CachePut有一些共有的属性:

属  性
类  型
描  述
value
String[]
要使用的缓存名称
condition
String
SpEL表达式,如果得到的值是false的话,不会将缓存应用到方法调用上
key
String
SpEL表达式,用来计算自定义的缓存key
unless
String
SpEL表达式,如果得到的值是true的话,返回值不会放到缓存之中

  •   在一个请求方法上加上@Cacheable注解,测试下效果
      

    @Cacheable(value="testallCache")  
    @RequestMapping(value = "/redis/user/{userId}", method = RequestMethod.GET)
      
    public User getUser(@PathVariable() Integer userId) {
      User user = userService.getUserById(userId);
      return user;
      
    }

  •   然后访问这个请求,控制台就报错啦。
      

    java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String  
    at org.springframework.data.redis.serializer.StringRedisSerializer.serialize(StringRedisSerializer.java:33)
      
    at org.springframework.data.redis.cache.RedisCacheKey.serializeKeyElement(RedisCacheKey.java:74)
      
    at org.springframework.data.redis.cache.RedisCacheKey.getKeyBytes(RedisCacheKey.java:49)
      
    at org.springframework.data.redis.cache.RedisCache$1.doInRedis(RedisCache.java:176)
      
    at org.springframework.data.redis.cache.RedisCache$1.doInRedis(RedisCache.java:172)
      
    at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:207)
      

      原因如下:
      
    先看一下Redis缓存默认的Key生成策略



    • If no params are given, return SimpleKey.EMPTY.
    • If only one param is given, return that instance.
    • If more the one param is given, return a SimpleKey containing all parameters.

  从上面的生成策略可以知道,上面的缓存testallCache使用的key是整形的userId参数,但是我们之前在redisTemplate里设置了template.setKeySerializer(new StringRedisSerializer());,所以导致类型转换错误。虽然也可以使用SpEL表达式生成Key(详见这里),但是返回结果还是需要是string类型(比如#root.methodName就是,#root.method就不是),更通用的办法是重写keyGenerator定制Key生成策略。


  •   修改RedisConfig类,重写keyGenerator方法:
      

    @Bean  
    public KeyGenerator keyGenerator() {
      return new KeyGenerator() {
      @Override
      public Object generate(Object target, Method method, Object... params) {
      StringBuilder sb = new StringBuilder();
      sb.append(target.getClass().getName());
      sb.append(":" + method.getName());
      for (Object obj : params) {
      sb.append(":" + obj.toString());
      }
      return sb.toString();
      }
      };
      
    }

  • 再次进行刚才的请求(分别以1,2作为userId参数),浏览器结果如下图:  
    DSC0000.jpg
      
    DSC0001.jpg
      
    使用redisclient工具查看下:
      
    DSC0002.jpg
      
    DSC0003.jpg
      
    可以看到Redis里保存了:



  • 两条string类型的键值对:key就是上面方法生成的结果,value就是user对象序列化成json的结果
  • 一个有序集合:其中key为@Cacheable里的value+~keys,分数为0,成员为之前string键值对的key
  这时候把userId为1的用户的username改为ansel(原来是ansel1),再次进行https://localhost:8443/redis/user/1 请求,发现浏览器返回结果仍是ansel1,证明确实是从Redis缓存里返回的结果。
  
DSC0004.jpg
  
DSC0005.jpg

缓存更新与删除


  •   更新与删除Redis缓存需要用到@CachePut和@CacheEvict。这时候我发现如果使用上面那种key的生成策略,以用户为例:它的增删改查方法无法保证生成同一个key(方法名不同,参数不同),所以修改一下keyGenerator,使其按照缓存名称+userId方式生成key:
      

    @Bean  
    public KeyGenerator keyGenerator() {
      return new KeyGenerator() {
      @Override
      public Object generate(Object target, Method method, Object... params) {
      StringBuilder sb = new StringBuilder();
      String[] value = new String[1];
      // sb.append(target.getClass().getName());
      // sb.append(":" + method.getName());
      Cacheable cacheable = method.getAnnotation(Cacheable.class);
      if (cacheable != null) {
      value = cacheable.value();
      }
      CachePut cachePut = method.getAnnotation(CachePut.class);
      if (cachePut != null) {
      value = cachePut.value();
      }
      CacheEvict cacheEvict = method.getAnnotation(CacheEvict.class);
      if (cacheEvict != null) {
      value = cacheEvict.value();
      }
      sb.append(value[0]);
      for (Object obj : params) {
      sb.append(":" + obj.toString());
      }
      return sb.toString();
      }
      };
      
    }

  •   接下来编写user的增删改查方法:
      

    @CachePut(value = "user", key = "#root.caches[0].name + ':' + #user.userId")  
    @RequestMapping(value = "/redis/user", method = RequestMethod.POST)
      
    public User insertUser(@RequestBody User user) {
      user.setPassword(SystemUtil.MD5(user.getPassword()));
      userService.insertSelective(user);
      return user;
      
    }
      

      
    @Cacheable(value = "user")
      
    @RequestMapping(value = "/redis/user/{userId}", method = RequestMethod.GET)
      
    public User getUser(@PathVariable Integer userId) {
      User user = userService.getUserById(userId);
      return user;
      
    }
      
    //#root.caches[0].name:当前被调用方法所使用的Cache, 即"user"
      
    @CachePut(value = "user", key = "#root.caches[0].name + ':' + #user.userId")
      
    @RequestMapping(value = "/redis/user", method = RequestMethod.PUT)
      
    public User updateUser(@RequestBody User user) {
      user.setPassword(SystemUtil.MD5(user.getPassword()));
      userService.updateByPrimaryKeySelective(user);
      return user;
      
    }
      

      
    @CacheEvict(value = "user")
      
    @RequestMapping(value = "/redis/user/{userId}", method = RequestMethod.DELETE)
      
    public void deleteUser(@PathVariable Integer userId) {
      userService.deleteByPrimaryKey(userId);
      
    }
      

      因为新增和修改传递的参数为user对象,keyGenerator无法获取到userId,只好使用SpEL显示标明key了。

然后进行测试:

进行insert操作:
DSC0006.jpg


插入后,进行get请求:
DSC0007.jpg


查看Redis存储:
DSC0008.jpg

  
DSC0009.jpg

进行update操作:
DSC00010.jpg


更新后,进行get请求:
DSC00011.jpg


查看Redis存储:
DSC00012.jpg


进行delete操作:
DSC00013.jpg


查看Redis存储:
DSC00014.jpg

  
发现user:3的记录已经没有了,只剩user:1,user:2了
  一直很想知道网上很多用之前那种keyGenerator方法的,他们是怎么进行缓存更新和删除的,有知道的可以告知下。
DSC00015.jpg

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-426209-1-1.html 上篇帖子: 自定义redis序列化工具 下篇帖子: 请叫我小冯哥哥
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表