SpringBoot(七) - Redis 缓存
1、五大基本数据类型和操作
1.1 字符串-string
命令 | 说明 |
---|---|
set key value | 如果key还没有,那就可以添加,如果key已经存在了,那会覆盖原有key的值 |
get key | 如果key还没有,获取为(nil),代表key没有被使用,如果key存在,可以获取对应key的值 |
exists key | 判断某个key是否存在,返回Integer值1 代表存在,如果 exists car2 则返回0,不存在 |
move key db | 将当前数据库存在的键值移动到其它数据库,其中db是数据库的序号 |
expire key 秒钟 | 为已经存在的key设置过期时间,注意过期之后,从内存中去掉了,是get不到的 |
ttl key | 查看还有多少秒过期,-1表示永不过期,-2表示已过期 |
type key | 命令用于返回 key 所储存的值的类型 |
del key | 根据key值删除 |
append key value | 根据key将其值进行字符串拼接 |
strlen key | 根据key获取其值的字符串长度,字节数 |
incr key | 对key对应数值进行加一操作,对应的字符串值必须是数值 |
decr key | 对key对应数值进行减一操作 |
incrby key 数值 | 对key对应数值按照指定的值进行递增 |
decrby key 数值 | 对key对应数值按照指定的值进行递减 |
getrange key 起始位置 结束位置 | 获取指定区间内的值,类似between。。。and的关系,起始位置为0,结束位置为-1 就是返回所有 |
setrange key 起始位置 具体值 | 设置指定区间内的值,具体值会从起始位置开始覆盖 |
setex key 过期秒值 真实值 | 设置带过期时间的key,动态设置。 |
setnx key value | 只有在 key 不存在时,才会设置 key 的值,如果已经存在了,不覆盖,设置不了; |
setnx key value | 如果返回0 代表没有设置成功,key对应值已经存在,如果返回1代表设置成功;这个就是redis的分布式锁命令,很重要; |
mset key1 val1 key2 val2 .... | 同时设置一个或多个 key-value 对 |
mget key1 key2 key3 .... | 获取所有(一个或多个)给定 key 的值。 |
msetnx key1 val1 key2 val2 ..... | 同时设置一个或多个 key-value 对,当且仅当所有给定 key 都不存在 |
1.2 列表-list
list操作起来类似于栈;
命令 | 说明 |
---|---|
lpush key val1 val2 val3 .... | 从左侧开始存放元素,先进后出 |
lrange key 起始位置 结束位置 | 从左侧开始,指定范围获取元素,-1代表所有 |
rpush key val1 val2 val3 .... | 从右侧开始存放元素,先进先出 |
lpop key | 从左侧一次取出一个元素 |
rpop key | 从右侧一次取出一个元素 |
lindex key index | 按照索引下标获得元素(从左到右,左下标从0开始,如果是-1代表最后一个,-2代表倒数第二个) |
llen key | 获取集合元素个数 |
lrem key 个数 具体的值 | 从左往右删除指定个数等于具体值的元素,返回的值为实际删除的数量,个数0,表示删除全部给定的值 |
ltrim key 开始index 结束index | 截取指定范围的值后再赋值给key |
rpoplpush 源列表 目的列表 | 移除列表的最后一个元素,并将该元素添加到另一个列表并返回 |
lset key index value | 将key集合中的指定下标位置值改为value |
linsert key before/after 值1 值2 | 在list某个已有 值1 的前后再添加具体 值2 |
小结:
- 它是一个字符串链表,left、right都可以插入添加;
- 如果键不存在,创建新的链表;
- 如果键已存在,新增内容;
- 如果值全移除,对应的键也就消失了;
- 链表的操作无论是头和尾效率都极高,但假如是对中间元素进行操作,效率就很惨淡了;
1.3 集合-set
命令 | 说明 |
---|---|
sadd key val1 val2 ... | 集合set中添加元素,如果有重复元素会自动去除 |
smembers key | 查看集合中的元素 |
sismember key val | 判断val是否在set集合中,如果在返回1 ,不在返回0 |
scard key | 获取集合里面的元素个数 |
srem key value | 删除集合中元素 |
srandmember key 某个整数 | 随机出几个数,如果超过最大数量就全部取出 |
srandmember key 某个整数 | 如果写的值是负数,比如-3 ,表示需要取出3个,但是可能会有重复值。 |
spop key | 随机出栈 |
smove key1 key2 | 将key1里的某个值赋给key2 |
sdiff key1 key2 | 在第一个set里面而不在后面任何一个set里面的项 |
sinter key1 key2 | 在两个set中都有的值的交集返回 |
sunion key1 key2 | 在两个set中所有的值的集合返回,会自动排除重复 |
1.4 键值对-hash
K V模式不变,但V是一个键值对;
命令 | 说明 |
---|---|
hset 父key 子key 子value | 将父key,增加子键值对,类似属性 |
hget 父key 子key | 获取父key,某个子key的值,获取属性值 |
hmset 父key 子key1 子val1 子key2 子val2 .... | 批量添加属性 |
hmget 父key 子key1 子key... | 批量获取属性 |
hgetall 父key | 批量获取属性及值 |
hdel 父key 子key | 删除子key属性及值 |
hlen 父key | 返回父key中的子key个数,相当于java实体的属性个数 |
hexists 父key 子key | 判断父key中是否包含某个子key,结果为1,代表存在 |
hkeys 父key | 获取父key中所有的子key |
hvals 父key | 获取父key中的所有的子val |
hincrby 父key 子key 值 | 给指定的子key值增加固定的值 |
hincrbyfloat 父key 子key 值 | 给有指定key的值增加小数 |
hsetnx 父key 子key 子val | 如果子key存在则失败,如果不存在则赋值 |
1.5 有序集合-zset
在set基础上,加一个score值。之前set是k1 v1 v2 v3,现在zset是k1 score1 v1 score2 v2;
命令 | 说明 |
---|---|
zadd key score1 val1 score2 val2 score3 val3 ... | 有序集合添加带score值的元素 |
zscore key val | 获取集合中某个值对应score值 |
zrange key 0 -1 [withscores] | zrange zset1 0 -1 ,结果为所有的值,不带分数;如:zrange zset1 0 -1 ,结果为所有的值,不带分数 |
zrange zset1 0 -1 withscores | 结果为所有的值和分数 |
zrangebyscore key 开始score 结束score | 获取score值在开始score-结束score之间的元素 |
zrangebyscore zset1 10 40 | 获取score值在10-40之间的元素,包含10和40 |
zrangebyscore zset1 10 (40 | 不包含40值;( 的含义是不包含 |
zrangebyscore zset1 (10 (40 | 不包含10,40值 |
zrangebyscore zset1 10 50 limit 2 2 | limit 结果的起始下标,获取的个数;limit 含义是限制获取的条数,相当于mysql的分页; |
zrem key 某score下对应的value值 | 删除元素 |
zcard key | 获取key对应的值的个数;注意score 和 value是一个整体 |
zcount key score区间 | 获取分值区间内元素个数 |
zrank key values值 | 获得下标值 |
zscore key 对应value值 | 获得value对应分数 |
zrevrank key value值 | 逆序获得对应逆序的下标值 |
zrevrange key 起始下标,结束下标 | 将之前顺序进行倒序 |
zrevrangebyscore key 结束score 开始score | 根据score值输出元素 |
zincrby key 增加分值 value值 | 给对应的值增加score值 |
2、Redis整合
2.1 spring-boot-starter-data-redis 依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2.2 redis配置
#端口号
server:
port: 8096
# redis配置
spring:
redis:
host: 127.0.0.1 #如果是redis远程服务器,此处redis服务器ip地址
port: 6379 #默认端口
# database: 0 #指定redis数据库,默认是0
# password: # 密码有就写,没有就省略
2.3 SpringBoot框架自动配置的redisTemplate
2.3.1 清空数据库
//自动装配 SpringBoot框架自动配置的redisTemplate
@Autowired
private RedisTemplate<Object,Object> redisTemplate;
//基于SpringBoot框架自动配置的redisTemplate,操作redis缓存
//获取连接
RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
//清空数据库中的所有数据
log.info("清空数据库中的所有数据");
connection.flushDb();
2.3.2 添加数据
//程序中,添加数据据到redis
log.info("------ 基于SpringBoot框架自动配置的redisTemplate 添加数据 ------");
redisTemplate.opsForValue().set("kh96_class_name","KGC_KH96");
redisTemplate.opsForValue().set("student_num",19);
2.3.3 获取数据
//程序中,从redis获取数据
log.info("------ 基于SpringBoot框架自动配置的redisTemplate 获取数据 ------");
log.info("****** 根据 班级的key:{},获取班级名称:{} ******","kh96_class_name",redisTemplate.opsForValue().get("kh96_class_name"));
log.info("****** 根据 班级的key:{},获取班级人数:{} ******","student_num",redisTemplate.opsForValue().get("student_num"));
2.3.4 修改值 (出现错误)
//程序中,基于SpringBoot框架自动配置的redisTemplate,操作redis缓存,存在问题
//场景:对班级人数进行增减操作,比如将班级人数,增加10
log.info("------ 基于SpringBoot框架自动配置的redisTemplate 操作数据 ------");
redisTemplate.opsForValue().increment("student_num",10);
//直接报错,会报500异常: redis.clients.jedis.exceptions.JedisDataException: ERR value is not an integer or out of range
//原因,通过系统默认的 redisTemplate,存放key和value值时,会自动使用Object类的序列化和反序列化,导致redis中真实存放的数据不是原始值,而是序列化后的值
数据结果:
2.4 自定义redisTemplate
2.4.1 fastjson 依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency>
2.4.2 自定义redisTemplate 配置类
//Redis自定义配置类,实现一个自定义序列化方式的 redisTemplate,提缓缓掉默认自动配置的 redisTemplate,实现String类型任意类型的value
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
// 自定义redisTemplate的模板对象
RedisTemplate<String, Object> template = new RedisTemplate<>();
// 设置连接工厂
template.setConnectionFactory(redisConnectionFactory);
//由于要通过程序操作远程的redis数据库,必须支持序列化,才可以让程序中的数据,在网络中传输
//定义String类型的序列化方式
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// 定义fastjson序列化方式,可以序列化任何对象
FastJsonRedisSerializer<Object> fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class);
// 需改为新的序列化方式
template.setKeySerializer(stringRedisSerializer);
template.setValueSerializer(fastJsonRedisSerializer);
template.setHashKeySerializer(stringRedisSerializer);
template.setHashValueSerializer(fastJsonRedisSerializer);
// 初始化为新的模板
template.afterPropertiesSet();
return template;
}
}
2.4.3 使用自定义redisTemplate 重新操作数据
//自动装配自定义 redisTemplate
@Autowired
private RedisTemplate<String,Object> redisTemplate;
//其他代码不变
操作结果:
2.5 自定义redisUtils工具类
2.5.1 自定义redisUtils工具类
---> RedisUtil 工具类
2.5.2 使用自定义redisTemplate和redisUtils工具类
@GetMapping("/testRedisUtils")
public String testSpringBootRedisUtils(){
//基于自定义的redisTemplate 和 RedisUtils 工具类,操作redis缓存
//程序中,添加数据据到redis
log.info("------ 基于自定义的redisTemplate 和 RedisUtils 工具类 添加数据 ------");
redisUtils.set("kh96_class_name_utils","KGC_KH96");
redisUtils.set("student_num_utils",19);
//程序中,从redis获取数据
log.info("------ 基于自定义的redisTemplate 和 RedisUtils 工具类 获取数据 ------");
log.info("****** 根据 班级的key:{},获取班级名称:{} ******","kh96_class_name_utils",redisUtils.get("kh96_class_name_utils"));
log.info("****** 根据 班级的key:{},获取班级人数:{} ******","student_num_utils",redisUtils.get("student_num_utils"));
//程序中,基于SpringBoot框架自动配置的redisTemplate,操作redis缓存
//场景:对班级人数进行增减操作,比如姜班级人数,增加10
log.info("------ 基于自定义的redisTemplate 和 RedisUtils 工具类 操作数据 ------");
redisUtils.incr("student_num_utils",10);
return "工具类 RedisUtils 操作 redis 成功!";
}
2.5.3 程序中如何存放对象到 redis
核心思想:一般都是姜对象转换为json字符串,存入redis,获取对象数据,就先获取json字符串,再转换为对应对象即可;
@GetMapping("/testRedisUtils")
public String testSpringBootRedisUtils(){
//程序中如何存放对象到 redis
//核心思想:一般都是姜对象转换为json字符串,存入redis,获取对象数据,就先获取json字符串,再转换为对应对象即可
//模拟用户登录成功后,将用户信息存入redis中,方便后续从redis中获取用户信息
User loginUser = User.builder().userId(1001).userName("KH96").userTel("135012030404").build();
//直接将对象存入redis即可
log.info("------ 基于自定义的redisTemplate 和 RedisUtils 工具类 存储对象 ------");
//自动把实体,通过fastjson的序列化方式,转发为JSON字符串存储
redisUtils.set(loginUser.getUserId().toString(),loginUser);
//模拟获取登录用户信息,直接从redis获取存入的JSON字符串,转换为目标用户对象
User realUser = JSON.parseObject(redisUtils.get(loginUser.getUserId().toString()).toString(),User.class);
log.info("------ 基于自定义的redisTemplate 和 RedisUtils 工具类获取对象:{} ",realUser);
return "工具类 RedisUtils 操作 redis 成功!";
}
数据结果:
3、练习
3.1 题目要求
实现商品评论点赞功能,要限制次数
1)功能:商品评论点赞,只能有一个接口,第一次请求是增加点赞,第二次请求就是取消点赞
2)限制:使用redis增加操作限制,点赞不能太频繁,比如:限制5s内最多点击4次,如果没有超出限制,可以正常操作,如果超出限制,返回提示:操作过于频繁,请稍后重试!
统一使用map做返回结果,内容必须包含:code:状态码,自定义,msg: success/fail,data:返回的数据内容
比如:获取验证码返回:
{
"code": 200,
"msg": "success",
"data": "手机号:xxxx,验证码:xxxx"
}
3.2 代码
/**
* @author : huayu
* @date : 19/10/2022
* @param : []
* @return : java.util.Map<java.lang.String,java.lang.Object>
* @description : 点赞操作
* 第一次请求是增加点赞,第二次请求就是取消点赞
* 限制5s内最多点击4次,如果没有超出限制,可以正常操作,如果超出限制,返回提示:操作过于频繁,请稍后重试!
*/
@GetMapping("/praise")
public Map<String,Object> praise(){
Map<String,Object> reMsg = new HashMap<>();
reMsg.put("code","200");
reMsg.put("msg","success");
//判断是不是第一次点击
if(redisUtils.get("praiseFlag") == null){
//第一点击, 设置 praiseFlag
redisUtils.set("praiseFlag",0);
}
//判断 是否有五秒限制
if(redisUtils.get("time") == null){
//第一次点击,或已经超过5秒
//添加 记录点击 次数
redisUtils.set("time",1,5);
}else {
//5秒内的操作
//判断 5秒中点击的次数 等于 4
if((Integer)redisUtils.get("time") == 4){
reMsg.put("code","500");
reMsg.put("msg","fail");
reMsg.put("data","操作过于频繁,请稍后重试!");
//返回信息
return reMsg;
}else {
//5秒内的 点击次数没有大于4次
redisUtils.incr("time",1);
}
}
//判断上一次是点赞还是取消点赞
if((Integer)redisUtils.get("praiseFlag") == 1){
//取消点赞操作
redisUtils.set("praiseFlag",0);
reMsg.put("data", "取消点赞成功!");
}else {
//点赞操作
redisUtils.set("praiseFlag",1);
reMsg.put("data", "点赞成功!");
}
//返回信息
return reMsg;
}
3.3 测试结果
3.3.1 第一次点击(五秒内)
3.3.2 第二次点击(五秒内)
3.3.3 第三次点击(五秒内)
3.3.4 第四次点击(五秒内)
3.3.5 第五次点击(五秒内)
4、浏览记录 练习
4.1 请求方法
@Autowired
RedisTemplate<String,Object> redisTemplate;
// 浏览记录,最新的浏览记录放在上面,最多展示10个,使用zset做,搜索关键字的score值使用日期毫秒,倒叙
@GetMapping("/addtHistory")
public RequestResult<Set<Object>> addtHistory(String uId,String pId){
//获取zset操作对象
ZSetOperations<String, Object> opsForZSet = redisTemplate.opsForZSet();
//放入记录
opsForZSet.add(uId,pId,System.currentTimeMillis());
Set<Object> historySet = null;
//获取记录数量
int historiesCount = opsForZSet.size(uId).intValue();
//判断否有10个记录
if(historiesCount >= 10) {
historySet = opsForZSet.reverseRange(uId,0, 9);
return ResultBuildUtil.success(historySet);
}else {
historySet = opsForZSet.rangeByScore(uId, 0, historiesCount);
return ResultBuildUtil.success(historySet);
}
}