Spring boot 论坛项目实战_04
1. Redis 入门
-
Redis 是一款 基于键值对 的NoSQL 数据库, 它的值支持多种数据结构:
-
字符串(Strings)、哈希(hashes)、列表(lists)、集合(sets)、有序集合(sorted sets)等
-
-
Redis 将所有的数据都存放在内存中,所以它的读写性能十分惊人。同时,Redis还可以将内存中的数据以快照或日志的形式保存到硬盘上,以保证数据的安全性
-
Redis 典型的应用场景包括:缓存、排行榜、计数器、社交网络、消息队列等。
-
参考网站:
-
Redis 常用命令:
-
-
字符串(Strings):
-
存储: set key【字符拼接用“ : ”】 value
-
获取: get key【字符拼接用“ : ”】
-
数字加:incr key,返回 integer
-
数字减:decr key,返回 integer
-
加减运算只能是 整数类型 integer
-
-
哈希(hashes):
-
存储:hset key【字符拼接用“ : ”】 field【hash 的 key】 value
-
获取:hget key【字符拼接用“ : ”】 field
-
-
列表(lists):【可以理解为一个横向的数组】
-
根据其存取 可左可右,可分别实现 队列 和 栈 的特性
-
进:可左可右
-
左进
-
左存多个数值: lpush key value [value ...]
-
查看当前列表长度: llen key
-
查看指定 key 的 list 里面第 index 个值:lindex key index
-
查看指定范围的值:lrange key start stop
-
-
出:可左可右
-
指定右出: rpop key
-
-
集合(sets):
-
存入集合元素:sadd key member [member ...]
-
统计集合元素:scard key
-
随机弹出元素:spop key 【可用于抽奖】
-
查看集合所剩元素:smembers key
-
-
有序集合(sorted sets):
-
添加元素:zadd key [NX|XX] [CH] [INCR] score member [score member ...] 【分数 元素名】
-
查询某一个值的分数:zscore key member
-
返回某个值的排名:zrank key member 【默认由小到大,从 0 开始】
-
取某个范围的数据:zrange key start stop [WITHSCORES]
-
-
全局:keys pattern
-
查看当前所有的 key :keys *
-
查看所有 test 开头的 key:keys test*
-
查看某个 key 的类型:type key
-
查看某个 key 是否存在:EXISTS key [key ...]
-
删除某 key:del key [key ...]
-
设置 某 key 的过期时间【到期自动删除】:expire key seconds
-
-
2. Spring 整合 Redis
-
引入依赖
-
spring-boot-starter-data-redis
-
-
配置Redis
-
Spring 默认配置的 k-v 中 k:Object
实际开放常用的 k: String 类型
所以这里需要自己再手动配置
-
配置数据库参数
-
编写配置类,构造RedisTemplate
-
// 基于 Spring 框架的 import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.RedisSerializer; @Configuration public class RedisConfig { // 接入连接工厂, 才能创建对象 @Bean public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory factory){ // 方法的实例化 RedisTemplate<String,Object> template = new RedisTemplate<String,Object>(); // 给对象设置连接工厂 template.setConnectionFactory(factory); // 设置 key 的序列化方式, 参数是 spring 框架的 redis 下的 // 返回一个能够序列化字符串的序列化器 template.setKeySerializer(RedisSerializer.string()); // 设置 value 的序列化方式 template.setValueSerializer(RedisSerializer.json()); // 设置 hash 的 key 的序列化方式 template.setHashKeySerializer(RedisSerializer.string()); // 设置 hash 的 value 的序列化方式 template.setHashValueSerializer(RedisSerializer.json()); // 触发设置结束后 生效 template.afterPropertiesSet(); return template; } }
-
-
-
访问Redis
-
redisTemplate.opsForValue()
-
@Test public void testStrings(){ String redisKey = "test:count"; // String 类型的值 redisTemplate.opsForValue().set(redisKey,1); System.out.println(redisTemplate.opsForValue().get(redisKey)); System.out.println(redisTemplate.opsForValue().increment(redisKey)); System.out.println(redisTemplate.opsForValue().decrement(redisKey)); }
-
-
redisTemplate.opsForHash()
-
// 访问 hash @Test public void testHashes(){ String redisKey = "test:user"; redisTemplate.opsForHash().put(redisKey,"id",1); redisTemplate.opsForHash().put(redisKey,"username","张三"); System.out.println(redisTemplate.opsForHash().get(redisKey, "id")); System.out.println(redisTemplate.opsForHash().get(redisKey, "username")); }
-
-
redisTemplate.opsForList()
-
@Test public void testLists(){ // 左进列表 String redisKey = "test:ids"; // 放入数据 redisTemplate.opsForList().leftPush(redisKey,101); redisTemplate.opsForList().leftPush(redisKey,102); redisTemplate.opsForList().leftPush(redisKey,103); // 取出数据 // 统计当前元素个数 System.out.println(redisTemplate.opsForList().size(redisKey)); // 获取指定位置的元素, 获取某个索引所对应的元素 System.out.println(redisTemplate.opsForList().index(redisKey,0)); // 按照 索引范围获取元素 System.out.println(redisTemplate.opsForList().range(redisKey, 0, 2)); // 弹出元素, 左出 System.out.println(redisTemplate.opsForList().leftPop(redisKey)); System.out.println(redisTemplate.opsForList().leftPop(redisKey)); System.out.println(redisTemplate.opsForList().leftPop(redisKey)); }
-
-
redisTemplate.opsForSet()
-
@Test public void testSets(){ String redisKey = "test:teachers"; redisTemplate.opsForSet().add(redisKey,"刘备","关羽","张飞","赵云","孔明"); // 统计元素个数 System.out.println(redisTemplate.opsForSet().size(redisKey)); // 弹出一个数据, 随机弹出 System.out.println(redisTemplate.opsForSet().pop(redisKey)); // 统计现在集合中的数据都是什么, 展示现在集合中所有元素 System.out.println(redisTemplate.opsForSet().members(redisKey)); }
-
-
redisTemplate.opsForZSet()
-
@Test public void testSortedSets(){ String redisKey = "test:students"; // 添加 元素, 及其对应的分数 redisTemplate.opsForZSet().add(redisKey,"唐僧",80); redisTemplate.opsForZSet().add(redisKey,"悟空",90); redisTemplate.opsForZSet().add(redisKey,"八戒",50); redisTemplate.opsForZSet().add(redisKey,"沙僧",70); redisTemplate.opsForZSet().add(redisKey,"龙马",60); // 统计元素总数 System.out.println(redisTemplate.opsForZSet().zCard(redisKey)); // 查询某个元素的分数 System.out.println(redisTemplate.opsForZSet().score(redisKey,"八戒")); // 查询某个元素的排名, 默认由小到大 System.out.println(redisTemplate.opsForZSet().rank(redisKey,"八戒")); // 倒叙排名, 按分数由大到小 System.out.println(redisTemplate.opsForZSet().reverseRank(redisKey,"八戒")); // 从小到大取前三 System.out.println(redisTemplate.opsForZSet().range(redisKey,0,2)); // 从大到小取前三 System.out.println(redisTemplate.opsForZSet().reverseRange(redisKey,0,2)); }
-
-
对 key 的访问
-
// 访问公共类型 key @Test public void testKeys(){ // 程序中一般不会用 keys * 这个命令, 这个命令一般是直接查询 redisTemplate.delete("test:user"); // 判断 key 是否存在 System.out.println(redisTemplate.hasKey("test:user")); // 设置 key 的过期时间 // TimeUnit. 设置时间 单位: 日, 时, 分, 秒, 毫秒... redisTemplate.expire("test:students",10, TimeUnit.SECONDS); }
-
-
多次访问同一个key , 可采用绑定 key 的方式,来避免复写 redisKey:
-
// 多次访问同一个 key @Test public void testBoundOperations(){ String redisKey = "test:count"; // Bound XXX Operations: XXX : 你具体要访问的数据类型, 这里是String // redisTemplate.bound XXX Ops : 绑定具体的数据类型, 这里是String BoundValueOperations operations = redisTemplate.boundValueOps(redisKey); // 这样就是绑定了 key 不用每次都写了. operations.increment(); operations.increment(); operations.increment(); operations.increment(); operations.increment(); System.out.println(operations.get()); }
-
-
Redis 的事务
Redis 是 非关系型数据库, 所以不用严格遵守 ACID
在实际生产环境中, 建议用编程式事务实现, 声明式只能针对方法整体不能 对单独代码行控制
// 编程式事务
@Test public void testTransactional() { Object obj = redisTemplate.execute(new SessionCallback() { @Override public Object execute(RedisOperations operations) throws DataAccessException { String redisKey = "test:tx"; // 启用事务 operations.multi(); operations.opsForSet().add(redisKey,"张三"); operations.opsForSet().add(redisKey,"李四"); operations.opsForSet().add(redisKey,"王五"); // 事务过程是不能允许读的 System.out.println(operations.opsForSet().members(redisKey)); //operations.exec() : 提交事务 return operations.exec(); } }); System.out.println(obj); }
-
打印结果可以看出:
-
第一次 查询结构为空, 保证了事务的原子性
-
第二次打印结果: 前面三个 “1”: 每次操作影响的元素个数; 后面就是这个集合内的具体元素
-
3. 点赞
将点赞数据存入 Redis 提升性能, 优于存在内存中
异步请求
-
点赞
-
支持对帖子、评论点赞
-
第1次点赞,第二次取消点赞【双击取消操作】
-
-
首页点赞数量
-
统计帖子的点赞数量
-
-
详情页点赞数量
-
统计点赞数量
-
显示点赞状态
-
4. 我收到的赞
-
重构点赞功能
-
以用户为key, 记录点赞数量
-
increment(key), decrement(key)
-
-
开发个人主页
-
以用户为key, 查询点赞数量
-
5. 关注、取消关注
-
需求
-
开发关注、取消关注功能
-
统计用户的关注数、粉丝数
-
-
关键
-
若 A 关注了 B, 则 A 是 B 的 Follower(粉丝) , B 是 A 的 Followee(目标)。
-
关注的目标可以是用户、帖子、题目等,在实现时将这些目标抽象为实体。
-
6. 关注列表、粉丝列表
-
业务层
-
查询某个用户关注的人,支持分页。
-
查询某个用户的粉丝,支持分页。
-
-
表现层
-
处理 “查询关注的人”、“查询粉丝” 请求。
-
编写 “查询关注的人”、“查询粉丝” 模板。
-
7. 优化登录模块
-
使用Redis 存储验证码
-
验证码需要频繁的访问与刷新,对性能要求较高
-
验证码不需永久保存,通常在很短的时间后就会失效
-
设置过期时间
-
-
分布式部署,存在Session 共享的问题
-
-
使用Redis 存储登录凭证【不删,保留用户记录】
-
处理每次请求时,都要查询用户的登录凭证,访问的效率非常高
-
-
使用Redis 缓存用户信息【暂存,要删+】
-