- 本文是个基于redis的分页查询实现
- 场景描述
- Redis分页自定义包装类
- 收藏和取消收藏biz业务处理
- 查询收藏数
- 查询用户收藏状态
- 分页查询我的收藏
本人业务开发中遇到了某种需要纯redis做分页查询的操作,但网上百度之后发现千篇一律,完全没有任何系统化东西呈现。故记录下自己想到的处理方式和代码实现。
ps:写的不好勿喷,实在是并没有找到对本人实用的东西。
需要实现收藏功能,或类似操作连贯性较高的非幂等性操作,且并发较高:
- 详情 能够实现对添加或删除收藏的功能
- 在 我的 界面能够对收藏目标进行分页查询。
- 对于数据,需要纯redis存储,并不考虑数据库一致性情况下。
public class RedisPage{ private int current = 1; private long total; private int size = 5; private long totalSize; private T data; @Override public String toString() { return "RedisPage{" + "current=" + current + ", total=" + total + ", size=" + size + ", data=" + data + '}'; } public int getCurrent() { return current; } public void setCurrent(int current) { if (current > 1) { this.current = current; } } public long getTotal() { return total; } public void setTotal(long total) { this.total = total; } public int getSize() { return size; } public void setSize(int size) { int minSize = 1, maxSize = 50; if (size >= minSize && size <= maxSize) { this.size = size; } } public T getData() { return data; } public void setData(T data) { this.data = data; } public int getFrom() { return (current - 1) * size; } public int getTo() { int start = getFrom(); return start + size - 1; } public boolean isEnd() { return current >= totalSize; } public long getTotalSize() { return totalSize; } public void setTotalSize() { this.totalSize = (total + size - 1) / size; } }
上述代码是用于redis的分页数据包装的,业务数据最终包在data参数中。
收藏和取消收藏biz业务处理 public void collect(CollectVo collectVo) {
String collectKey = RedisKeyUtil.getCollectKey(collectVo);
String userKey = RedisKeyUtil.getUserKey(collectVo);
String userValueKey = RedisKeyUtil.getUserValueKey(collectVo);
redisTemplate.execute(new SessionCallback
上述代码babybus:首先生成三个key,如下表格所示,先开启一个事务,使所有操作一块执行,然后rank操作首先对collectKey对应的userId的key进行排序,rank操作如果这个key不存在的话返回是空的,所以可以利用这个特点对数据进行判断,如果用户收藏了此对象,则不为空,就进行移除操作,也就是取消收藏,反之添加,在进行存储zset的索引列表时使用时间戳赋予分值进行排序,这样收藏的列表即为有序的了
需要注意:此处的事务并不是真正意义上的事务,只是为了同时执行一批操作,防止多次存取,因为redis不支持事务
| redis key | 含义 | 格式 | 类型 |
|---|---|---|---|
| collectKey | 被收藏的对象key,保存value为用户id (便于统计该对象被多少人收藏,被哪些人收藏) | collect::1:id(key前缀+收藏类型+id) | zset |
| userKey | 用户收藏了哪些对象key,保存value为收藏对象id (作用同上,用于索引数据) | user_collect::id(key前缀+用户id) | zset |
| userValueKey | 用户收藏的索引对应的数据值(相应的业务实体) | user_collect_value::id(key前缀+用户id) | hash |
查询收藏数上述表格展示了三个redis key,全部的业务数据存储就使用到了这三个key,可以理解为数据库的二级索引,userKey只保存具体收藏对象的id并不存储实际数据,实际数据由hash类型的userValueKey来存储,可以看到该key由用户id进行区分,具体保存的键为收藏对象id,值为具体收藏对象业务实体。
这样设计的主要原因就是分离开之后可以对业务操作进行更多的更方便的查询,也是为什么没有像一级索引一样直接保存了业务数据而单独抽出来了一个索引key
public Long findCollectCount(CollectVo collectVo) {
String collectKey = RedisKeyUtil.getCollectKey(collectVo);
//直接size一下就获取到了
Long size = redisTemplate.opsForZSet().size(collectKey);
return Objects.nonNull(size) ? size : 0L;
}
查询用户收藏状态
public Boolean findCollectStatus(CollectVo collectVo) {
String collectKey = RedisKeyUtil.getCollectKey(collectVo);
//rank一下如果收藏对象key里有该用户id则收藏
Long rank = redisTemplate.opsForZSet().rank(collectKey, collectVo.getUserId());
return rank != null;
}
分页查询我的收藏
入参vo:
public class MyCollectVo {
@NotNull
//当前页
private Integer current;
@NotNull
//每页条数
private Integer size;
@Override
public String toString() {
return "MyCollectVo{" +
"current=" + current +
", size=" + size +
'}';
}
public Integer getCurrent() {
return current;
}
public void setCurrent(Integer current) {
this.current = current;
}
public Integer getSize() {
return size;
}
public void setSize(Integer size) {
this.size = size;
}
}
//这两行不需要特别关注,就是将入参转为上述的redis分页包装类page,然后collect参数里面主要用到一个userId,进行第二行业务查询 RedisPage> page = convertApiUtil.getPage(collectVo); RedisPage
> collectArticle = collectionService.getCollectArticle(page, collect);
真正的getCollectArticle业务处理如下
public RedisPage> getCollectArticle(RedisPage> page, CollectVo collectVo) {
//上述的用户收藏了哪些对象id的key 也就是用户收藏索引key
String userKey = RedisKeyUtil.getUserKey(collectVo);
//用户收藏的索引值key
String userValueKey = RedisKeyUtil.getUserValueKey(collectVo);
//索引列表 倒序 reverseRange操作分页获取倒序的索引列表
Set
上述代码里有很多的注释解释,就不一一说明了,到此完结。



