SpringBoot整合Redis实现缓存

redis是一个基于内存的高性能key-value数据库,具有极高的读写速度。本文介绍 SpringBoot 和 Redis 的整合,以及如何在项目中具体应用。  

一、我的需求

1、实时快速获得动态数据 如图,下图中的通知数5和消息数8在每个页面都是有的,不可能每次都从 MySQL 数据库里查询一下吧(下拉的详细信息是点击事件后才ajax加载)。我们知道 MySQL 是从硬盘读取,Redis 是从内存读取,速度比较可想而知。 所以我们这里可以使用 Redis 数据库,第一次先把计数从 MySQL 查出来,然后放到 Redis 数据库里,下次直接从 Redis 数据库读取。张三给李四发一条消息,只需要给李四的 Redis 数据库里该 key 记录 + 1,李四读取了这条消息,只需要给李四的 Redis 数据库里该 key -1。     2、排行榜或推荐内容(每天变动一次或七天变一次) 比如我这里右边侧边栏有本周用户排行榜(按文章数排序),推荐关键词排行榜(按文章关键词次数排序),热门文章排行榜(按评论数点赞数访问数等加权排序)。MySQL查询到数据后,我们还有进行处理排序,时间比较长。如果我们第一次查询后,然后将现成的数据存到 Redis 数据库中,下次访问就很快了,无论用户如何刷新页面,加载都很快。 Redis 也可以设置缓存过期时间,比如用户排行榜设置1小时更新一次,推荐关键词只有增删改的时候才更新,热门文章2小时更新一次。  

二、基本准备

1、本地安装了 Redis 数据库 Linux 或 Mac 可以参考这篇文章:点此   2、启动查看端口   3、下载可视化客户端 rdm 通常情况下,我们可以在命令行下查看 Redis 数据库,但是可视化工具能更真实地让我们看到数据的结构。    

三、开始代码

注意:本文用的 Redis 数据结构都是 String 类型,没有用到 List 类型 本文使用的 SpringBoot 版本 1.5.9 如果你的是 2.0 版本,在 RedisConfig.java 需要修改一下序列化工具部分   1、Maven
  1. <!-- 里面依赖了spring-data-redis -->
  2. <dependency>
  3.     <groupId>org.springframework.boot</groupId>
  4.     <artifactId>spring-boot-starter-data-redis</artifactId>
  5. </dependency>
  2、封装 redisTemplate 工具类
  1. package com.liuyanzhao.forum.util;
  2. import org.springframework.beans.factory.annotation.Autowired;
  3. import org.springframework.data.redis.core.StringRedisTemplate;
  4. import org.springframework.stereotype.Component;
  5. import java.util.Map;
  6. import java.util.Set;
  7. import java.util.concurrent.TimeUnit;
  8. /**
  9.  * @author 言曌
  10.  * @date 2018/3/18 下午1:11
  11.  */
  12. @Component
  13. public class RedisOperator {
  14. //  @Autowired
  15. //    private RedisTemplate<String, Object> redisTemplate;
  16.     @Autowired
  17.     private StringRedisTemplate redisTemplate;
  18.     // Key(键),简单的key-value操作
  19.     /**
  20.      * 实现命令:TTL key,以秒为单位,返回给定 key的剩余生存时间(TTL, time to live)。
  21.      *
  22.      * @param key
  23.      * @return
  24.      */
  25.     public long ttl(String key) {
  26.         return redisTemplate.getExpire(key);
  27.     }
  28.     /**
  29.      * 实现命令:expire 设置过期时间,单位秒
  30.      *
  31.      * @param key
  32.      * @return
  33.      */
  34.     public void expire(String key, long timeout) {
  35.         redisTemplate.expire(key, timeout, TimeUnit.SECONDS);
  36.     }
  37.     /**
  38.      * 实现命令:INCR key,增加key一次
  39.      *
  40.      * @param key
  41.      * @return
  42.      */
  43.     public long incr(String key, long delta) {
  44.         return redisTemplate.opsForValue().increment(key, delta);
  45.     }
  46.     /**
  47.      * 实现命令: key,减少key一次
  48.      *
  49.      * @param key
  50.      * @return
  51.      */
  52.     public long decr(String key, long delta) {
  53.         if(delta<0){
  54. //            throw new RuntimeException("递减因子必须大于0");
  55.             del(key);
  56.             return 0;
  57.         }
  58.         return redisTemplate.opsForValue().increment(key, -delta);
  59.     }
  60.     /**
  61.      * 实现命令:KEYS pattern,查找所有符合给定模式 pattern的 key
  62.      */
  63.     public Set<String> keys(String pattern) {
  64.         return redisTemplate.keys(pattern);
  65.     }
  66.     /**
  67.      * 实现命令:DEL key,删除一个key
  68.      *
  69.      * @param key
  70.      */
  71.     public void del(String key) {
  72.         redisTemplate.delete(key);
  73.     }
  74.     // String(字符串)
  75.     /**
  76.      * 实现命令:SET key value,设置一个key-value(将字符串值 value关联到 key)
  77.      *
  78.      * @param key
  79.      * @param value
  80.      */
  81.     public void set(String key, String value) {
  82.         redisTemplate.opsForValue().set(key, value);
  83.     }
  84.     /**
  85.      * 实现命令:SET key value EX seconds,设置key-value和超时时间(秒)
  86.      *
  87.      * @param key
  88.      * @param value
  89.      * @param timeout (以秒为单位)
  90.      */
  91.     public void set(String key, String value, long timeout) {
  92.         redisTemplate.opsForValue().set(key, value, timeout, TimeUnit.SECONDS);
  93.     }
  94.     /**
  95.      * 实现命令:GET key,返回 key所关联的字符串值。
  96.      *
  97.      * @param key
  98.      * @return value
  99.      */
  100.     public String get(String key) {
  101.         return (String) redisTemplate.opsForValue().get(key);
  102.     }
  103.     // Hash(哈希表)
  104.     /**
  105.      * 实现命令:HSET key field value,将哈希表 key中的域 field的值设为 value
  106.      *
  107.      * @param key
  108.      * @param field
  109.      * @param value
  110.      */
  111.     public void hset(String key, String field, Object value) {
  112.         redisTemplate.opsForHash().put(key, field, value);
  113.     }
  114.     /**
  115.      * 实现命令:HGET key field,返回哈希表 key中给定域 field的值
  116.      *
  117.      * @param key
  118.      * @param field
  119.      * @return
  120.      */
  121.     public String hget(String key, String field) {
  122.         return (String) redisTemplate.opsForHash().get(key, field);
  123.     }
  124.     /**
  125.      * 实现命令:HDEL key field [field ...],删除哈希表 key 中的一个或多个指定域,不存在的域将被忽略。
  126.      *
  127.      * @param key
  128.      * @param fields
  129.      */
  130.     public void hdel(String key, Object... fields) {
  131.         redisTemplate.opsForHash().delete(key, fields);
  132.     }
  133.     /**
  134.      * 实现命令:HGETALL key,返回哈希表 key中,所有的域和值。
  135.      *
  136.      * @param key
  137.      * @return
  138.      */
  139.     public Map<Object, Object> hgetall(String key) {
  140.         return redisTemplate.opsForHash().entries(key);
  141.     }
  142.     // List(列表)
  143.     /**
  144.      * 实现命令:LPUSH key value,将一个值 value插入到列表 key的表头
  145.      *
  146.      * @param key
  147.      * @param value
  148.      * @return 执行 LPUSH命令后,列表的长度。
  149.      */
  150.     public long lpush(String key, String value) {
  151.         return redisTemplate.opsForList().leftPush(key, value);
  152.     }
  153.     /**
  154.      * 实现命令:LPOP key,移除并返回列表 key的头元素。
  155.      *
  156.      * @param key
  157.      * @return 列表key的头元素。
  158.      */
  159.     public String lpop(String key) {
  160.         return (String) redisTemplate.opsForList().leftPop(key);
  161.     }
  162.     /**
  163.      * 实现命令:RPUSH key value,将一个值 value插入到列表 key的表尾(最右边)。
  164.      *
  165.      * @param key
  166.      * @param value
  167.      * @return 执行 LPUSH命令后,列表的长度。
  168.      */
  169.     public long rpush(String key, String value) {
  170.         return redisTemplate.opsForList().rightPush(key, value);
  171.     }
  172. }
  3、application.properties
  1. #### Redis
  2. # Redis数据库索引,默认为0
  3. spring.redis.database=0
  4. # Redis 服务器地址
  5. spring.redis.host=127.0.0.1
  6. # Redis 端口
  7. spring.redis.port=6379
  8. # Redis 密码,默认为空
  9. spring.redis.password=
  10. # 连接池中最大连接数(使用负值表示没有限制)
  11. spring.redis.pool.max-active=1000
  12. # 连接池中最大阻塞等待时间(使用负值表示没有限制)
  13. spring.redis.pool.max-wait=-1
  14. # 连接池中最大空闲连接
  15. spring.redis.pool.max-idle=10
  16. # 连接池中最小空闲连接
  17. spring.redis.pool.min-idle=2
  18. # 连接超时时间(毫秒)
  19. spring.redis.timeout=0
  4、RedisConfig.java
  1. package com.liuyanzhao.forum.config;
  2. import com.fasterxml.jackson.annotation.JsonAutoDetect;
  3. import com.fasterxml.jackson.annotation.PropertyAccessor;
  4. import com.fasterxml.jackson.databind.ObjectMapper;
  5. import org.springframework.cache.CacheManager;
  6. import org.springframework.cache.annotation.EnableCaching;
  7. import org.springframework.context.annotation.Bean;
  8. import org.springframework.context.annotation.Configuration;
  9. import org.springframework.data.redis.cache.RedisCacheManager;
  10. import org.springframework.data.redis.connection.RedisConnectionFactory;
  11. import org.springframework.data.redis.core.RedisTemplate;
  12. import org.springframework.data.redis.core.StringRedisTemplate;
  13. import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
  14. import java.util.HashMap;
  15. import java.util.Map;
  16. /**
  17.  * @author 言曌
  18.  * @date 2018/5/21 上午10:02
  19.  */
  20. @Configuration
  21. //@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 3600 * 12)//最大过期时间
  22. @EnableCaching
  23. public class RedisConfig {
  24.     //缓存管理器
  25.     @Bean
  26.     public CacheManager cacheManager(@SuppressWarnings("rawtypes") RedisTemplate redisTemplate) {
  27.         RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);
  28.         //设置缓存过期时间
  29.         Map<String, Long> expires = new HashMap<>();
  30.         expires.put("7d"7 * 3600 * 24L);
  31.         expires.put("12h"3600 * 12L);
  32.         expires.put("1h"3600 * 1L);
  33.         expires.put("10m"60 * 10L);
  34.         cacheManager.setExpires(expires);
  35.         cacheManager.setDefaultExpiration(7 * 3600 * 24);//默认缓存七天
  36.         return cacheManager;
  37.     }
  38.     @Bean
  39.     public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) {
  40.         StringRedisTemplate template = new StringRedisTemplate(factory);
  41.         setSerializer(template);//设置序列化工具
  42.         template.afterPropertiesSet();
  43.         return template;
  44.     }
  45.     private void setSerializer(StringRedisTemplate template) {
  46.         @SuppressWarnings({"rawtypes""unchecked"})
  47.         Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
  48.         ObjectMapper om = new ObjectMapper();
  49.         om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
  50.         om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
  51.         jackson2JsonRedisSerializer.setObjectMapper(om);
  52.         template.setValueSerializer(jackson2JsonRedisSerializer);
  53.     }
  54. }
 

四、简单的测试

新建一个控制器,测试一下之前封装的 RedisOperator
  1. package com.liuyanzhao.forum.controller;
  2. import com.alibaba.fastjson.JSON;
  3. import com.liuyanzhao.forum.entity.Article;
  4. import com.liuyanzhao.forum.entity.User;
  5. import com.liuyanzhao.forum.repository.ArticleRepository;
  6. import com.liuyanzhao.forum.util.RedisOperator;
  7. import org.springframework.beans.factory.annotation.Autowired;
  8. import org.springframework.web.bind.annotation.RequestMapping;
  9. import org.springframework.web.bind.annotation.ResponseBody;
  10. import org.springframework.web.bind.annotation.RestController;
  11. import java.util.List;
  12. /**
  13.  * @author 言曌
  14.  * @date 2018/3/18 上午11:18
  15.  */
  16. @RestController
  17. @RequestMapping("/redis")
  18. public class RedisController {
  19.     @Autowired
  20.     private RedisOperator redis;
  21.     @Autowired
  22.     private ArticleRepository articleRepository;
  23.     @RequestMapping("/set")
  24.     @ResponseBody
  25.     public String set(){
  26.         redis.set("name","琪仔");
  27.         redis.incr("age",20);
  28.         User user = new User();
  29.         user.setId(234);
  30.         List<Article> articleList = articleRepository.findAll();
  31.         System.out.println(articleList);
  32.         redis.set("json:articleList", JSON.toJSONString(articleList));
  33.         return  "success";
  34.     }
  35.     @RequestMapping("/get")
  36.     @ResponseBody
  37.     public String get(){
  38.         return redis.get("json:articleList");
  39.     }
  40. }
 

五、具体应用到项目中

1、Sevice 中统计某个用户未阅读的消息数量,并添加到Redis
  1. @Override
  2.    @Cacheable(value = "12h", key = "'messageSize:'+#p0.id")
  3.    public Integer countNotReadMessageSize(User user) {
  4.        return messageRepository.countByUserAndStatus(user, MessageStatusEnum.NOT_READ_MESSAGE.getCode());
  5.    }
    2、发布私信(好友的未读消息+1)
  1. @PostMapping("/messages")
  2. public ResponseEntity<Response> createMessage(Integer friendId, String content) {
  3. .......
  4.         //3、Redis 中 好友的未读消息+1
  5.         redisOperator.incr("messageSize:" + friendId, 1L);
  6. .....
  7. }
  3、查看私信(自己的未读消息-N)
  1. @GetMapping("/manage/messages")
  2.    public ModelAndView messages( Integer uid) {
  3.           ......
  4.            // 查看好友发来的N消息,Redis中未读消息-N
  5.            redisOperator.decr("messageSize:" + user.getId(), notReadSize);
  6.           ......
  7.    }
   

六、@Cacheable、@CachePut、@CacheEvict 注解的使用

  1. @Transactional
  2.     @Override
  3.     //执行下面的方法,最终将结果添加(如果这个key存在,则覆盖)到Redis中
  4.     @CachePut(value = "7d", key = "'tags:'+#p0.id", unless = "#tag eq null")
  5.     public Tag saveTag(Tag tag) {
  6.         return tagRepository.save(tag);
  7.     }
  8.     @Override
  9.     //如果Redis数据库中没有这个key,执行下面方法,最终结果添加到Redis。如果key已存在,则直接从Redis获取,不执行下面方法
  10.     @Cacheable(value = "7d", key = "'tags:'+#id.toString()")
  11.     public Tag getTagById(Integer id) {
  12.         return tagRepository.findOne(id);
  13.     }
  14.     @Override
  15.     //condition为true执行@CacheEvict,将该key从Redis删除
  16.     @CacheEvict(value = "7d", key = "'tags:'+#id.toString()", condition = "#result eq true")
  17.     public Boolean removeTag(Integer id) {
  18.         tagRepository.delete(id);
  19.         return true;
  20.     }
注意:key 必须为String类型, @Cacheable中不能使用 #Result   除了上述使用方法参数作为key之外,Spring还为我们提供了一个root对象可以用来生成key。通过该root对象我们可以获取到以下信息。 参考这里:https://www.cnblogs.com/fashflying/p/6908028.html
属性名称 描述 示例
methodName 当前方法名 #root.methodName
method 当前方法 #root.method.name
target 当前被调用的对象 #root.target
targetClass 当前被调用的对象的class #root.targetClass
args 当前方法参数组成的数组 #root.args[0]
caches 当前被调用的方法使用的Cache #root.caches[0].name
 

七、更多文章

Mac环境下安装配置Redis

Redis 命令大全

SpringBoot Redis 设置缓存过期时间

 

发表评论

目前评论:1