利用redis ZSet 有序集合实现可靠滚动分页 即在有新的数据插入分页查询不会查询重复或者遗漏数据

传统的分页 前端参数一般传入当前页数curpage和页面长度paegsize 最终通过数据库limit curpage*(pageszie-1),pageszie 实现分页 假设两参数分别为1,5 即 limit 0,5 也就是查询序号0到4的5条数据       这时如果数据库新增了一条数据其序号为1。

如果查询下一页即limit 5,5 查询序号为5 到9的数据

java redis page 分页 redis set分页_java

 

如图所示,很显然值为四的数据被重复查了。 查了下比较流行的做法就是新增一个字段,记录数据插入的时间。 然后查寻第一页的时候记录当前时间,之后每次分页查询都需要带上这个时间 把比这个时间大的数据排除。该方案挺不错,但是要修改数据库,费事.

我的方案是借助redis 的有序集合 ,每次插入数据库成功时,额外保存一份<数据id,插入时间>到redis的有序集合里 。这时就可以通过插入时间分页了。第一次查询返回按时间排序前1到5的数据,   然后记录当前的时间6 。  之后的查询带上这个时间。 返回从 比这个时间小的第一个数据(即为5)和其后的四条数据。

。如图:

时间戳

10

9

8

7

6

5

4

3

2

1

10

9

8

7

6

5

4

3

2

1


第一页






时间戳

11

10

9

8

7

6

5

4

3

2

1

新插入的数据

10

9

8

7

6

5

4

3

2

1



第一页

第二页

但是这也有个问题 假如同一时间新增很多条数据怎么办。

时间戳

10

9

8

7

6

6

6

5

4

3

2

1

10

9

8

7

6

6

6

5

4

3

2

1

引入个新的变量offset 记录返回的数据中有几个和 他们最后一个数据时间相同 上图

第一次查询返回 10 9 8 7 6 6即为2。 然后下一次查询的参数即为最后一个数据的时间戳6 ,偏移量2 ,就能确定是从第三个6开始了。表达能力太差了说不清楚 直接上代码:


@PostMapping
    public Result saveBlog(@RequestBody Blog blog) {
        // 获取登录用户
        UserDTO user = UserHolder.getUser();
        Long userId = user.getId();
        blog.setUserId(userId);
        // 保存探店博客
        if (blogService.save(blog)) {
            Long currTime = System.currentTimeMillis();
//        博客推送给关注作者的人
//        1获得关注该作者的用户列表
            List<Follow> follows = followService.query().select("user_id").eq("follow_user_id", userId).list();
            for (Follow follow : follows
            ) {
                Long followId = follow.getUserId();
//
                String key = RedisConstants.FEED_KEY + followId;
                stringRedisTemplate.opsForZSet().add(key,String.valueOf(blog.getId()),currTime);
            }
            // 返回id
            return Result.ok(blog.getId());
        }
        return Result.fail("发布笔记失败");
    }


@Override
    public Result queryBlogByFollow(Long max, Integer offset) {
        // 1.获取当前用户
        Long userId = UserHolder.getUser().getId();
        // 2.查询收件箱 ZREVRANGEBYSCORE key Max Min LIMIT offset count
        String key = RedisConstants.FEED_KEY + userId;
        Set<ZSetOperations.TypedTuple<String>> typedTuples = stringRedisTemplate.opsForZSet()
                .reverseRangeByScoreWithScores(key, 0, max, offset, 2);
        // 3.非空判断
        if (typedTuples == null || typedTuples.isEmpty()) {
            return Result.ok();}
        // 4.解析数据:blogId、minTime(时间戳)、offset
        List<Long> ids = new ArrayList<>(typedTuples.size());
        long minTime = 0; // 2
        int os = 1; // 2
        for (ZSetOperations.TypedTuple<String> tuple : typedTuples) { // 5 4 4 2 2
            // 4.1.获取id
            ids.add(Long.valueOf(tuple.getValue()));
            // 4.2.获取分数(时间戳)
            long time = tuple.getScore().longValue();
            if (time == minTime) {
                os++;
            } else {
                minTime = time;
                os = 1;
            }
        }
        os = minTime == max ? os : os + offset;
        // 5.根据id查询blog
        String idStr = StrUtil.join(",", ids);
        List<Blog> blogs = query().in("id", ids).last("ORDER BY FIELD(id," + idStr + ")").list();

        for (Blog blog : blogs) {
            // 5.1.查询blog有关的用户
            queryBlogUser(blog);
            // 5.2.查询blog是否被点赞
//            isBlogLiked(blog);
        }

        // 6.封装并返回
        ScrollResult r = new ScrollResult();
        r.setList(blogs);
        r.setOffset(os);
        r.setMinTime(minTime);

        return Result.ok(r);
    }