栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

redis分页查询代码实现

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

redis分页查询代码实现

redis分页查询 简单明了代码实现
  • 本文是个基于redis的分页查询实现
    • 场景描述
    • Redis分页自定义包装类
    • 收藏和取消收藏biz业务处理
    • 查询收藏数
    • 查询用户收藏状态
    • 分页查询我的收藏

本文是个基于redis的分页查询实现

本人业务开发中遇到了某种需要纯redis做分页查询的操作,但网上百度之后发现千篇一律,完全没有任何系统化东西呈现。故记录下自己想到的处理方式和代码实现。
ps:写的不好勿喷,实在是并没有找到对本人实用的东西。

场景描述

需要实现收藏功能,或类似操作连贯性较高的非幂等性操作,且并发较高:

  1. 详情 能够实现对添加或删除收藏的功能
  2. 在 我的 界面能够对收藏目标进行分页查询。
  3. 对于数据,需要纯redis存储,并不考虑数据库一致性情况下。
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() {
            @Override
            public Object execute(@NonNull RedisOperations redis) throws DataAccessException {
                Long member = redisTemplate.opsForZSet().rank(collectKey, collectVo.getUserId());
                redis.multi();
                if (null != member) {
                    redisTemplate.opsForZSet().remove(collectKey, collectVo.getUserId());
                    redisTemplate.opsForZSet().remove(userKey, collectVo.getCollectId());
                    redisTemplate.opsForHash().delete(userValueKey, collectVo.getCollectId());
                } else {
                    redisTemplate.opsForZSet().add(collectKey, collectVo.getUserId(), System.currentTimeMillis());
                    redisTemplate.opsForZSet().add(userKey, collectVo.getCollectId(), System.currentTimeMillis());
                    redisTemplate.opsForHash().put(userValueKey, collectVo.getCollectId(), collectVo);
                }
                return redis.exec();
            }
        });
    }
 

上述代码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 articleKey = redisTemplate.opsForZSet().reverseRange(userKey, page.getFrom(), page.getTo());
        //查询收藏总数
        Long size = redisTemplate.opsForZSet().size(userKey);
        if (Objects.isNull(articleKey) || Objects.isNull(size)) {
            throw new CommonException("查询size或索引列表为空!");
        }
        //设置数据总量
        page.setTotal(size);
        //设置总页数
        page.setTotalSize();
        //设置是否最后一页
        page.isEnd();
        //判空
        if (page.getCurrent() > page.getTotalSize() || articleKey.isEmpty()) {
            page.setData(new ArrayList<>());
            return page;
        }
        //接下来用到了多线程操作,因为多条数据需要多次从索引值key中获取,所以进行了异步操作,可以去掉
        //此处用map把set索引列表按序附上一个自增的score便于排序
        //因为通过reverseRange查出来的set是有序的,但下方异步操作查询之后结果是乱序的需要重排序
        ConcurrentHashMap keyMap = new ConcurrentHashMap<>(20);
        int score = 0;
        for (Object a : articleKey) {
            keyMap.put(a, score++);
        }
        //声明个计数器,异步操作时执行完一批后返回
        final CountDownLatch countDownLatch = new CountDownLatch(articleKey.size());
        //线程安全列表包一下,操作的时候不会出问题
        List countList = Collections.synchronizedList(new ArrayList<>());
        //索引值
        for (Map.Entry index : keyMap.entrySet()) {
            MyThread.getExecutor().submit(() -> {
                try {
                	//for循环从索引值列表里通过索引列表的id取
                    CollectVo artValue = (CollectVo) redisTemplate.opsForHash().get(userValueKey, index.getKey());
                    //获取索引值分数(时间戳,收藏时间)
                    Double timestamp = redisTemplate.opsForZSet().score(userKey, index.getKey());
                    CollectArticleVo collectArticleVo = new CollectArticleVo();
                    //此处为时间格式化,并不一定要有
                    if (timestamp != null) {
                        LocalDateTime dateTime = LocalDateTime.ofEpochSecond(timestamp.longValue() / 1000L, 0, ZoneOffset.ofHours(8));
                        String format = dateTime.format(DateTimeFormatter.ofPattern(PATTERN));
                        collectArticleVo.setCollectTime(format);
                    }
                    collectArticleVo.setId(index.getKey().toString().substring(index.getKey().toString().lastIndexOf(SPLIT) + 1));
                    collectArticleVo.setIntroduce(Objects.requireNonNull(artValue).getCollectIntroduce());
                    collectArticleVo.setSort(index.getValue());
                    countList.add(collectArticleVo);
                } catch (Exception exception) {
                    log.error("查询我的收藏出错了: ", exception);
                } finally {
                    countDownLatch.countDown();
                }
            });
        }
        try {
            countDownLatch.await();
        } catch (InterruptedException exception) {
            log.error("查询我的收藏出错了: ", exception);
        }
        //最后进行重排序
        Collections.sort(countList);
        page.setData(countList);
        return page;
    }
 

上述代码里有很多的注释解释,就不一一说明了,到此完结。

转载请注明:文章转载自 www.mshxw.com
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号