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
<!-- 里面依赖了spring-data-redis --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
2、封装 redisTemplate 工具类
package com.liuyanzhao.forum.util; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Component; import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; /** * @author 言曌 * @date 2018/3/18 下午1:11 */ @Component public class RedisOperator { // @Autowired // private RedisTemplate<String, Object> redisTemplate; @Autowired private StringRedisTemplate redisTemplate; // Key(键),简单的key-value操作 /** * 实现命令:TTL key,以秒为单位,返回给定 key的剩余生存时间(TTL, time to live)。 * * @param key * @return */ public long ttl(String key) { return redisTemplate.getExpire(key); } /** * 实现命令:expire 设置过期时间,单位秒 * * @param key * @return */ public void expire(String key, long timeout) { redisTemplate.expire(key, timeout, TimeUnit.SECONDS); } /** * 实现命令:INCR key,增加key一次 * * @param key * @return */ public long incr(String key, long delta) { return redisTemplate.opsForValue().increment(key, delta); } /** * 实现命令: key,减少key一次 * * @param key * @return */ public long decr(String key, long delta) { if(delta<0){ // throw new RuntimeException("递减因子必须大于0"); del(key); return 0; } return redisTemplate.opsForValue().increment(key, -delta); } /** * 实现命令:KEYS pattern,查找所有符合给定模式 pattern的 key */ public Set<String> keys(String pattern) { return redisTemplate.keys(pattern); } /** * 实现命令:DEL key,删除一个key * * @param key */ public void del(String key) { redisTemplate.delete(key); } // String(字符串) /** * 实现命令:SET key value,设置一个key-value(将字符串值 value关联到 key) * * @param key * @param value */ public void set(String key, String value) { redisTemplate.opsForValue().set(key, value); } /** * 实现命令:SET key value EX seconds,设置key-value和超时时间(秒) * * @param key * @param value * @param timeout (以秒为单位) */ public void set(String key, String value, long timeout) { redisTemplate.opsForValue().set(key, value, timeout, TimeUnit.SECONDS); } /** * 实现命令:GET key,返回 key所关联的字符串值。 * * @param key * @return value */ public String get(String key) { return (String) redisTemplate.opsForValue().get(key); } // Hash(哈希表) /** * 实现命令:HSET key field value,将哈希表 key中的域 field的值设为 value * * @param key * @param field * @param value */ public void hset(String key, String field, Object value) { redisTemplate.opsForHash().put(key, field, value); } /** * 实现命令:HGET key field,返回哈希表 key中给定域 field的值 * * @param key * @param field * @return */ public String hget(String key, String field) { return (String) redisTemplate.opsForHash().get(key, field); } /** * 实现命令:HDEL key field [field ...],删除哈希表 key 中的一个或多个指定域,不存在的域将被忽略。 * * @param key * @param fields */ public void hdel(String key, Object... fields) { redisTemplate.opsForHash().delete(key, fields); } /** * 实现命令:HGETALL key,返回哈希表 key中,所有的域和值。 * * @param key * @return */ public Map<Object, Object> hgetall(String key) { return redisTemplate.opsForHash().entries(key); } // List(列表) /** * 实现命令:LPUSH key value,将一个值 value插入到列表 key的表头 * * @param key * @param value * @return 执行 LPUSH命令后,列表的长度。 */ public long lpush(String key, String value) { return redisTemplate.opsForList().leftPush(key, value); } /** * 实现命令:LPOP key,移除并返回列表 key的头元素。 * * @param key * @return 列表key的头元素。 */ public String lpop(String key) { return (String) redisTemplate.opsForList().leftPop(key); } /** * 实现命令:RPUSH key value,将一个值 value插入到列表 key的表尾(最右边)。 * * @param key * @param value * @return 执行 LPUSH命令后,列表的长度。 */ public long rpush(String key, String value) { return redisTemplate.opsForList().rightPush(key, value); } }
3、application.properties
#### Redis # Redis数据库索引,默认为0 spring.redis.database=0 # Redis 服务器地址 spring.redis.host=127.0.0.1 # Redis 端口 spring.redis.port=6379 # Redis 密码,默认为空 spring.redis.password= # 连接池中最大连接数(使用负值表示没有限制) spring.redis.pool.max-active=1000 # 连接池中最大阻塞等待时间(使用负值表示没有限制) spring.redis.pool.max-wait=-1 # 连接池中最大空闲连接 spring.redis.pool.max-idle=10 # 连接池中最小空闲连接 spring.redis.pool.min-idle=2 # 连接超时时间(毫秒) spring.redis.timeout=0
4、RedisConfig.java
package com.liuyanzhao.forum.config; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.cache.RedisCacheManager; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; import java.util.HashMap; import java.util.Map; /** * @author 言曌 * @date 2018/5/21 上午10:02 */ @Configuration //@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 3600 * 12)//最大过期时间 @EnableCaching public class RedisConfig { //缓存管理器 @Bean public CacheManager cacheManager(@SuppressWarnings("rawtypes") RedisTemplate redisTemplate) { RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate); //设置缓存过期时间 Map<String, Long> expires = new HashMap<>(); expires.put("7d", 7 * 3600 * 24L); expires.put("12h", 3600 * 12L); expires.put("1h", 3600 * 1L); expires.put("10m", 60 * 10L); cacheManager.setExpires(expires); cacheManager.setDefaultExpiration(7 * 3600 * 24);//默认缓存七天 return cacheManager; } @Bean public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) { StringRedisTemplate template = new StringRedisTemplate(factory); setSerializer(template);//设置序列化工具 template.afterPropertiesSet(); return template; } private void setSerializer(StringRedisTemplate template) { @SuppressWarnings({"rawtypes", "unchecked"}) 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.setValueSerializer(jackson2JsonRedisSerializer); } } 四、简单的测试 新建一个控制器,测试一下之前封装的 RedisOperator package com.liuyanzhao.forum.controller; import com.alibaba.fastjson.JSON; import com.liuyanzhao.forum.entity.Article; import com.liuyanzhao.forum.entity.User; import com.liuyanzhao.forum.repository.ArticleRepository; import com.liuyanzhao.forum.util.RedisOperator; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; import java.util.List; /** * @author 言曌 * @date 2018/3/18 上午11:18 */ @RestController @RequestMapping("/redis") public class RedisController { @Autowired private RedisOperator redis; @Autowired private ArticleRepository articleRepository; @RequestMapping("/set") @ResponseBody public String set(){ redis.set("name","琪仔"); redis.incr("age",20); User user = new User(); user.setId(234); List<Article> articleList = articleRepository.findAll(); System.out.println(articleList); redis.set("json:articleList", JSON.toJSONString(articleList)); return "success"; } @RequestMapping("/get") @ResponseBody public String get(){ return redis.get("json:articleList"); } }
五、具体应用到项目中
1、Sevice 中统计某个用户未阅读的消息数量,并添加到Redis
@Override @Cacheable(value = "12h", key = "'messageSize:'+#p0.id") public Integer countNotReadMessageSize(User user) { return messageRepository.countByUserAndStatus(user, MessageStatusEnum.NOT_READ_MESSAGE.getCode()); }
2、发布私信(好友的未读消息+1)
@GetMapping("/manage/messages") public ModelAndView messages( Integer uid) { ...... // 查看好友发来的N消息,Redis中未读消息-N redisOperator.decr("messageSize:" + user.getId(), notReadSize); ...... }
六、@Cacheable、@CachePut、@CacheEvict 注解的使用
@Transactional @Override //执行下面的方法,最终将结果添加(如果这个key存在,则覆盖)到Redis中 @CachePut(value = "7d", key = "'tags:'+#p0.id", unless = "#tag eq null") public Tag saveTag(Tag tag) { return tagRepository.save(tag); } @Override //如果Redis数据库中没有这个key,执行下面方法,最终结果添加到Redis。如果key已存在,则直接从Redis获取,不执行下面方法 @Cacheable(value = "7d", key = "'tags:'+#id.toString()") public Tag getTagById(Integer id) { return tagRepository.findOne(id); } @Override //condition为true执行@CacheEvict,将该key从Redis删除 @CacheEvict(value = "7d", key = "'tags:'+#id.toString()", condition = "#result eq true") public Boolean removeTag(Integer id) { tagRepository.delete(id); return true; }
注意:key 必须为String类型, @Cacheable中不能使用 #Result
除了上述使用方法参数作为key之外,Spring还为我们提供了一个root对象可以用来生成key。通过该root对象我们可以获取到以下信息。
参考这里:https://www.cnblogs.com/fashflying/p/6908028.html
还木有评论哦,快来抢沙发吧~