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

news2024/11/16 10:17:00

软件环境:

  • 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在里面搜索不到,所以手动加上

  <dependency>
      <groupId>com.github.pagehelper</groupId>
      <artifactId>pagehelper</artifactId>
      <version>5.1.8</version>
  </dependency>

动手实践

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


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);
	}

}

写好分页需要的参数类

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类,返回分页的参数信息

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和加载数据逻辑

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

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接口

 @PostMapping(value = "/pageUserInfo")
 public ResultBean<PageDataBean<UserVo>> pageUserInfo(@RequestBody PageBean pageBean) {
     return ResultBean.ok(userHandler.pageUserInfo(pageBean));
 }

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

 Set<String> recordSet = Optional.ofNullable(redisTemplate
         .opsForZSet()
         .reverseRange(REDIS_KEY, 0, num))
         .orElse(CollUtil.newHashSet());

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1284661.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

云原生之深入解析如何限制Kubernetes集群中文件描述符与线程数量

一、背景 linux 中为了防止进程恶意使用资源&#xff0c;系统使用 ulimit 来限制进程的资源使用情况&#xff08;包括文件描述符&#xff0c;线程数&#xff0c;内存大小等&#xff09;。同样地在容器化场景中&#xff0c;需要限制其系统资源的使用量。ulimit: docker 默认支持…

【Vue】使用 Vue CLI 脚手架创建 Vue 项目(使用命令行创建)

前言 在开始使用Vue进行开发之前&#xff0c;我们需要先创建一个Vue项目。Vue CLI&#xff08;Command Line Interface&#xff09;是一个官方提供的脚手架工具&#xff0c;可以帮助我们快速创建Vue项目。 步骤 打开终端或命令行工具&#xff0c;运行以下命令&#xff1a; vu…

传输层可靠传输的原理

目录 1.停止等待协议 2.连续ARQ协议 3.TCP报文段的首部格式 4.TCP的滑动窗口机制 &#xff08;1&#xff09;发送窗口 &#xff08;2&#xff09;接收窗口 &#xff08;3&#xff09;发送缓存 5.超时重传时间的选择 6.选择确认SACK(Selective ACK) 7.使用滑动窗口实现…

TCL - 库编译过程和官方手册

文章目录 TCL - 库编译过程和官方手册概述笔记编译步骤TCL官方手册END TCL - 库编译过程和官方手册 概述 想看看sqlite3的官方demo工程, 没看到. 想编译一下sqlite3源码, 看看编译后有没有example 工程. 看了sqlite3的官方说明, 他们工程使用tcl来编译的. 一听tcl, 咋这么耳熟…

基于Docker构建Python开发环境

1. Dockerfile dockerfile所在目录结构 FROM python:3.8 WORKDIR /leo RUN apt-get install -y wget RUN /bin/cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo Asia/Shanghai >/etc/timezone # ssh免密登录 COPY id_rsa.pub /leo RUN mkdir ~/.s…

戴森发布全新Airstrait吹风直发器,美发科技品类再添力作

——利用气流&#xff0c;吹干的同时拉直头发&#xff0c;无需热夹板&#xff0c;头发无热损伤 &#xff08;2023年11月30日&#xff0c;上海&#xff09;戴森今日重磅发布全新美发造型产品——戴森Airstrait™吹风直发器&#xff0c;作为戴森美发科技品类的又一创新力作&…

Redis Hash数据类型

Redis Hash数据类型 几乎所有的主流编程语言都提供了哈希(hash)类型&#xff0c;它们的叫法可能是哈希、字典、关联数组、映射。在 Redis 中&#xff0c;哈希类型是指值本身又是一个键值对结构&#xff0c;形如key “key”&#xff0c;value {ffield1, value1 }, … {fieldN…

算法通关村第十六关-黄金挑战滑动窗口与堆的结合

大家好我是苏麟 , 今天带来一道小题 . 滑动窗口最大值 描述 : 给你一个整数数组 nums&#xff0c;有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。 返回 滑动窗口中的最大值 。 题目 : …

探索图像生成中的生成对抗网络 (GAN) 世界

一、介绍 生成对抗网络&#xff08;GAN&#xff09;的出现标志着人工智能领域的一个重要里程碑&#xff0c;特别是在图像生成领域。GAN 由 Ian Goodfellow 和他的同事于 2014 年提出&#xff0c;代表了机器学习中的一种新颖方法&#xff0c;展示了生成高度逼真和多样化图像的能…

ping会出现的两种问题-----time out 和 unreachable

ping命令常见的返回信息有两种: Request timed out和Destination host unreachable 两者的区别是: Request timed out是ping包没有返回的路由&#xff0c;导致超时 Destination host unreachable是ping包没有去到目的地的路由 来看一个例子&#xff1a; 各部件配置如下&…

电子学会C/C++编程等级考试2022年09月(四级)真题解析

C/C++等级考试(1~8级)全部真题・点这里 第1题:最长上升子序列 一个数的序列bi,当b1 < b2 < … < bS的时候,我们称这个序列是上升的。对于给定的一个序列(a1, a2, …, aN),我们可以得到一些上升的子序列(ai1, ai2, …, aiK),这里1 <= i1 < i2 < … &l…

React全站框架Next.js使用入门

Next.js是一个基于React的服务器端渲染框架&#xff0c;它可以帮助我们快速构建React应用程序&#xff0c;并具有以下优势&#xff1a; 1. 支持服务器端渲染&#xff0c;提高页面渲染速度和SEO&#xff1b; 2. 自带webpack开发环境&#xff0c;实现即插即用的特性&#xff1b;…

新华三数字大赛复赛知识点 网络访问控制

EAD解决方案、portal认证、以太网访问控制列表 EAD&#xff1a; 网络安全从本质上讲是管理问题&#xff0c;&#xff08;End user Admission Domination&#xff09;解决方案从控制用户终端安全接入网络的角度入手&#xff0c;整合网络接入控制与终端安全产品&#xff0c;通过…

nginx对多个服务器的高可用,容易出现鉴权失败

高可用简单测试正常&#xff0c;但是出现高概率401鉴权错误 抓包发现&#xff0c;确实是401 &#xff0c; 而鉴权是两次交互&#xff1a; 抓包发现鉴权到不同服务器上了&#xff0c;导致鉴权没有完成。 此时就需要我们的ip_hash,把同一IP地址的请求,都分配给同一台后端服务器&…

【mysql】基于binlog数据恢复指令和坑

文章目录 1.binlog相关配置是否开启binlogbinlog日志格式 2.导出binlog日志mysqlbinlog指令updateinsertdeletebinlog中的事件 3.数据恢复4.特别注意的坑为什么bash脚本执行mysqlbinlog&#xff0c;无法找到指令为什么执行mysqlbinlog&#xff0c;无法数据恢复 1.binlog相关配置…

互联网Java工程师面试题·Spring Boot篇·第一弹

目录 1、什么是 Spring Boot&#xff1f; 2、Spring Boot 有哪些优点&#xff1f; 3、什么是 JavaConfig&#xff1f; 4、如何重新加载 Spring Boot 上的更改&#xff0c;而无需重新启动服务器&#xff1f; 5、Spring Boot 中的监视器是什么&#xff1f; 6、如何在 Sprin…

stm32一种步进电机查表法驱动

文章目录 一、定时器基础频率二、驱动原理三、关键代码 对于stm32芯片来说&#xff0c;步进电机的驱动由于要在中断中不断计算下一次脉冲的时间而极其消耗算力&#xff0c;使用计算的方法对于芯片的算法消耗更高&#xff0c;特别是在f1这种算力比较低的芯片上&#xff0c;这时候…

【数电笔记】25-mos管的开关特性

目录 说明&#xff1a; mos管的符号 1. N沟道增强型 2. P沟道增强型 3. N沟道耗尽型 4. P沟道耗尽型 mos管的静态开关特性 1. N沟道增强型MOS管 2. P沟道增强型MOS管 说明&#xff1a; 笔记配套视频来源&#xff1a;B站&#xff1b;本系列笔记并未记录所有章节&#…

(C语言)判定一个字符串是否是另一个字符串的子串,若是则返回子串在主串中的位置。

要求&#xff1a; &#xff08;1&#xff09;在主函数中输入两个字符串&#xff0c;调用子函数cmpsubstr()判断&#xff0c;并在主函数输出结果。 &#xff08;2&#xff09;子函数的返回值为-1表示未找到&#xff0c;否则返回子串的位置&#xff08;起始下标&#xff09;。 …

883重要知识点

&#xff08;1&#xff09;程序结构分三种&#xff1a;顺序结构&#xff0c;选择结构&#xff0c;循环结构。 &#xff08;2&#xff09;该程序都要从main&#xff08;&#xff09;开始&#xff0c;然后从最上面往下。 &#xff08;3&#xff09;计算机的数据在电脑中保存以二…