前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >SpringBoot系列之使用Redis ZSet实现排序分页

SpringBoot系列之使用Redis ZSet实现排序分页

作者头像
SmileNicky
发布2023-12-05 14:30:37
8690
发布2023-12-05 14:30:37
举报
文章被收录于专栏:Nicky's blog

软件环境:

  • JDK 1.8
  • SpringBoot 2.2.1
  • Maven 3.2+
  • Mysql 8.0.26
  • spring-boot-starter-data-redis 2.2.1
  • jedis3.1.0
  • 开发工具
    • IntelliJ IDEA
    • smartGit

实现思路

相对于set来说,sorted set是一种有序的set,排序是根据每个元素的score排序的,score相同时根据key的ASCII码排序

根据ZSET的个性,我们可以实现一个排序,同时有个序号,也可以实现分页的逻辑,下面给出一个例子,看看具体的实现

项目搭建

使用Spring官网的https://start.spring.io快速创建Spring Initializr项目

选择maven、jdk版本

选择需要的依赖

因为pagehelper在里面搜索不到,所以手动加上

代码语言:javascript
复制
  <dependency>
      <groupId>com.github.pagehelper</groupId>
      <artifactId>pagehelper</artifactId>
      <version>5.1.8</version>
  </dependency>

动手实践

为了方便测试,写一个测试类,批量写入数据

代码语言:javascript
复制
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.TimeInterval;
import cn.hutool.json.JSONUtil;
import com.example.redis.model.dto.UserDto;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.temporal.ChronoUnit;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;

@SpringBootTest
class SpringbootRedisApplicationTests {

    private static final String REDIS_KEY = "testKeyRecord";

 	@Resource
    private RedisTemplate redisTemplate;

	 @Test
	 void testPipeline() {
	     TimeInterval timeInterval = DateUtil.timer();
	     Map<Long, String> map = new HashMap<>();
	     IntStream.range(0, 10000).forEach(e->{
	         Long increment = getNextId();
	         UserDto userDto = UserDto.builder()
	                 .id(increment)
	                 .name("user"+increment)
	                 .age(100)
	                 .email("123456@qq.com")
	                 .build();
	         map.put(increment, JSONUtil.toJsonStr(userDto));
	     });
	     redisTemplate.executePipelined(new RedisCallback<Object>() {
	         @Override
	         public Object doInRedis(RedisConnection connection) throws DataAccessException {
	             map.forEach((score,value)->{
	                 connection.zSetCommands().zAdd(REDIS_KEY.getBytes(), score, value.getBytes());
	                 connection.expire(REDIS_KEY.getBytes(), getExpire(new Date()));
	             });
	             return null;
	         }
	     });
	     System.out.println("执行时间:"+timeInterval.intervalRestart()+"ms");
	 }
	
	private Long getNextId() {
	    String idKey = String.format("testKeyId%s",  DateUtil.format(new Date() , DatePattern.PURE_DATE_PATTERN));
	    Long increment = redisTemplate.opsForValue().increment(idKey);
	    redisTemplate.expire(idKey, getExpire(new Date()), TimeUnit.SECONDS);
	    return increment;
	}

	public static Long getExpire(Date currentDate) {
	    LocalDateTime midnight = LocalDateTime.ofInstant(currentDate.toInstant(),
	            ZoneId.systemDefault()).plusDays(1).withHour(0).withMinute(0)
	            .withSecond(0).withNano(0);
	    LocalDateTime currentDateTime = LocalDateTime.ofInstant(currentDate.toInstant(),
	            ZoneId.systemDefault());
	    return ChronoUnit.SECONDS.between(currentDateTime, midnight);
	}

}

写好分页需要的参数类

代码语言:javascript
复制
package com.example.redis.common.page;

import lombok.Data;

@Data
public class PageObject {
    // 当前页
    private long pageNum;
    // 当前页数
    private long pageSize;
    // 总页数
    private long totalPage;
    // 总数量
    private long totalCount;

}

写好一个PageDataBean类,返回分页的参数信息

代码语言:javascript
复制
package com.example.redis.common.page;


import lombok.Data;

import java.util.ArrayList;
import java.util.List;

@Data
public class PageDataBean<T> {

    private List<T> dataList = new ArrayList<>();

    private PageObject pageObj = new PageObject();

    public PageDataBean(List<T> dataList , Long totalCount , Integer pageSize , Integer pageNum) {
        this.dataList = dataList;
        pageObj.setPageNum(pageNum);
        pageObj.setPageSize(pageSize);
        pageObj.setTotalCount(totalCount);
        pageObj.setTotalPage(totalCount / pageSize + (totalCount % pageSize == 0 ? 0 : 1));
    }

}

PageBean传入需要的参数,并实现initPage和加载数据逻辑

代码语言:javascript
复制
package com.example.redis.common.page;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.List;

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class PageBean {

    // 当前页
    private Integer pageNum;
    // 一页的条数
    private Integer pageSize;

    @JsonIgnore
    private Page pages;

    public void initPage() {
        this.pages = PageHelper.startPage(pageNum , pageSize);
    }

    public PageDataBean loadData(List dataList) {
        return new PageDataBean(dataList , pages.getTotal() , pageNum , pageSize);
    }


}

分页核心逻辑,主要是使用reverseRange使用倒序和分页的逻辑,如果要正序,可以使用range

代码语言:javascript
复制
package com.example.redis.handler;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.example.redis.common.page.PageBean;
import com.example.redis.common.page.PageDataBean;
import com.example.redis.model.vo.UserVo;
import com.github.pagehelper.Page;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.List;
import java.util.Optional;
import java.util.Set;

@Component
public class UserHandler {

    private static final String REDIS_KEY = "testKeyRecord";

    @Resource
    private RedisTemplate redisTemplate;

    public PageDataBean<UserVo> pageUserInfo(PageBean pageBean) {
        Integer pageNum = Optional.ofNullable(pageBean.getPageNum()).orElse(1);
        Integer pageSize = Optional.ofNullable(pageBean.getPageSize()).orElse(10);
        pageBean.initPage();

        if (!redisTemplate.hasKey(REDIS_KEY)) {
            return pageBean.loadData(CollUtil.newArrayList());
        }
        int min = (pageNum -1) * pageSize;
        int max = min + pageSize - 1 ;
        Long size = redisTemplate.opsForZSet().size(REDIS_KEY);

        Set<String> recordSet = Optional.ofNullable(redisTemplate
                .opsForZSet()
                .reverseRange(REDIS_KEY, min, max))
                .orElse(CollUtil.newHashSet());
        List<UserVo> list = CollUtil.newArrayList();
        recordSet.stream().forEach(getValue -> {
            if (StrUtil.isNotBlank(getValue)) {
                UserVo recordVo = null;
                try {
                    recordVo = JSONUtil.toBean(getValue, UserVo.class);
                } catch (Exception e) {
                    // ignore exception
                }
                if (recordVo != null) {
                    list.add(recordVo);
                }
            }
        });

        Page page = new Page();
        page.setTotal(size);
        pageBean.setPages(page);

        return pageBean.loadData(list);
    }

}

分页查询的api接口

代码语言:javascript
复制
 @PostMapping(value = "/pageUserInfo")
 public ResultBean<PageDataBean<UserVo>> pageUserInfo(@RequestBody PageBean pageBean) {
     return ResultBean.ok(userHandler.pageUserInfo(pageBean));
 }

补充: 如果是要获取倒排的最后几条数据,就可以使用

代码语言:javascript
复制
 Set<String> recordSet = Optional.ofNullable(redisTemplate
         .opsForZSet()
         .reverseRange(REDIS_KEY, 0, num))
         .orElse(CollUtil.newHashSet());
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023-12-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 软件环境:
  • 实现思路
  • 项目搭建
  • 动手实践
相关产品与服务
云数据库 Redis®
腾讯云数据库 Redis®(TencentDB for Redis®)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档