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

软件环境:

  • 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());

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Nginx(十二) gzip gzip_static sendfile directio aio 组合使用测试(2)

测试10&#xff1a;开启gzip、sendfile、aio、directio1m&#xff0c;关闭gzip_static&#xff0c;请求/index.js {"time_iso8601":"2023-11-30T17:20:5508:00","request_uri":"/index.js","status":"200","…

SpringBoot整合ES客户端操作

SpringBoot整合ES客户端操作 介绍ES ES下载与安装 https://www.elastic.co/cn/downloads/past-releases 不要装太新的&#xff0c;里面自己配置了jdk&#xff0c;太新的可能用不了&#xff0c;免安装的&#xff0c;解压就好 浏览器输入&#xff1a;http://localhost:9200/ 返…

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

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

U-GAT-IT 使用指南:人脸动漫风格化

U-GAT-IT 使用指南 网络结构优化目标 论文地址&#xff1a;https://arxiv.org/pdf/1907.10830.pdf 项目代码&#xff1a;https://github.com/taki0112/UGATIT U-GAT-IT 和 Pix2Pix 的区别&#xff1a; U-GAT-IT&#xff1a;主要应用于图像风格转换、图像翻译和图像增强等任务…

上传文件获得下载链接方法:直链!直链!

&#xff01;非 百度网盘 不是直接用网盘下载&#xff0c;要用直链&#xff0c;百度上有很多方法。 我自己研究了个&#xff0c;跳过百度网盘输密码进网页的方法 还是先还是要把文件上传网盘让后搜索网盘获取直链的方法&#xff08;那百度网盘举例&#xff09; 地址 https:…

项目部署到线上服务器后,报 Redis error: ERR unknown command del 错误

查了很多资料&#xff0c;终于解决了&#xff0c;问题出在redis.conf里&#xff0c;该文件里被添加了新的命令如下&#xff1a; 在这几句命令前加 # 号注释掉&#xff0c;重启即可解决 另附上相关redis的命令&#xff1a; 停止Redis&#xff1a;systemctl stop redis启动Redis…

JavaEE进阶学习:Spring Boot 配置文件

1.配置文件的作用 整个项目中所有重要的数据都是在配置文件中配置的&#xff0c;比如&#xff1a; 数据库的连接信息&#xff08;包含用户名和密码的设置&#xff09;&#xff1b;项目的启动端口&#xff1b;第三方系统的调用秘钥等信息&#xff1b;用于发现和定位问题的普通…

大数据项目——基于Django协同过滤算法的房源可视化分析推荐系统的设计与实现

大数据项目——基于Django协同过滤算法的房源可视化分析推荐系统的设计与实现 技术栈&#xff1a;大数据爬虫/机器学习学习算法/数据分析与挖掘/大数据可视化/Django框架/Mysql数据库 本项目基于 Django框架开发的房屋可视化分析推荐系统。这个系统结合了大数据爬虫、机器学习…

深入理解Go语言GC机制

1、Go 1.3之前的标记-清除&#xff08;mark and sweep&#xff09;算法 Go 1.3之前的时候主要用的是普通的标记-清除算法&#xff0c;此算法主要由两个主要的步骤&#xff1a; 标记&#xff08;Mark phase&#xff09;清除&#xff08;Sweep phase&#xff09; 1&#xff09…

通达OA inc/package/down.php接口存在未授权访问漏洞

声明 本文仅用于技术交流&#xff0c;请勿用于非法用途 由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c;文章作者不为此承担任何责任。 一. 产品简介 通达OA&#xff08;Office Anywhere网络智能办公系统&am…

图像语义分割算法(FCN/U-net)

Some definitions &#xfeff; 与目标检测不同&#xff0c;语义分割任务不但要对图片中的物体的位置和类别进行预测&#xff0c;还要精确地描绘出不同类物体之间的边界&#xff08;注意是不同类物体&#xff0c;而不是不同物体。若对同一类的不同物体也进行区分&#xff0c;则…

ERPNext SQL 注入漏洞复现

0x01 产品简介 ERPNext 是一套开源的企业资源计划系统。 0x02 漏洞概述 ERPNext 系统frappe.model.db_query.get_list 文件 filters 参数存在 SQL 注入漏洞,攻击者除了可以利用 SQL 注入漏洞获取数据库中的信息(例如,管理员后台密码、站点的用户个人信息)之外,甚至在高权…

同旺科技 USB TO SPI / I2C --- 调试W5500_TCP Client测试

所需设备&#xff1a; 内附链接 1、USB转SPI_I2C适配器(专业版); 首先&#xff0c;连接W5500模块与同旺科技USB TO SPI / I2C适配器&#xff0c;如下图&#xff1a; 网关IP地址寄存器(192.168.1.1)子网掩码寄存器(255.255.255.0)源MAC地址寄存器源IP地址寄存器(192.168.1.8)…

idea__SpringBoot微服务01——了解Springboot

了解Springboot 一、回顾学习与现在三、回顾什么是Spring三、Spring是如何简化Java开发的四、什么是SpringBoot五、看图————————创作不易&#xff0c;如觉不错&#xff0c;随手点赞&#xff0c;关注&#xff0c;收藏(*&#xffe3;︶&#xffe3;)&#xff0c;谢谢~~ 一…

互联网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…

Mysql集群部署---MySQL集群Cluster将数据分成多个片段,每个片段存储在不同的服务器上

1.1 目的 部署MysqlCluster集群环境 1.2 MySQL集群Cluster原理 1 数据分片 MySQL集群Cluster将数据分成多个片段&#xff0c;每个片段存储在不同的服务器上。这样可以将数据负载分散到多个服务器上&#xff0c;提高系统的性能和可扩展性。 2. 数据同步 MySQL集群Cluster使…

微服务--一篇入门kubernets

Kubernetes 1. Kubernetes介绍1.1 应用部署方式演变1.2 kubernetes简介1.3 kubernetes组件1.4 kubernetes概念 2. kubernetes集群环境搭建2.1 前置知识点2.2 kubeadm 部署方式介绍2.3 安装要求2.4 最终目标2.5 准备环境2.6 系统初始化2.6.1 设置系统主机名以及 Host 文件的相互…

两种内网穿透的实现方法

目录 前言&#xff1a; 一、IP和端口的作用 二、公网IP不够用 三、内网穿透实现方法 方法一&#xff1a;设置路由器 方法二&#xff1a;使用某些APP&#xff0c;例如花生壳 前言&#xff1a; 本文会介绍为什么需要使用内网穿透以及实现内网穿透的两种方法 一、IP和端口…

sqlmap400报错问题解决

python sqlmap.py -r sql.txt --batch --techniqueB --tamperspace2comment --risk 3 --force-ssl–batch 选项全部默认 不用再手动输入 –techniqueB 使用布尔盲注&#xff0c;该参数是指出要求使用的注入方式 –tamperspace2comment使用特殊脚本&#xff0c;space2comment是把…

LeedCode刷题---双指针问题

顾得泉&#xff1a;个人主页 个人专栏&#xff1a;《Linux操作系统》 《C/C》 《LeedCode刷题》 键盘敲烂&#xff0c;年薪百万&#xff01; 双指针简介 常见的双指针有两种形式&#xff0c;一种是对撞指针&#xff0c;一种是左右指针。 对撞指针:一般用于顺序结构中&…