分页-PageHelper原理以及实时分页-键集分页

news2024/11/16 1:56:40

一.PageHelper原理

1.使用

PageHelper 是国内非常优秀的一款开源 mybatis 分页插件,它支持常用的主流数据库,例如 Oracle、Mysql、MariaDB、SQLite、Hsqldb 等。

PageHelper 的安装很简单,只需要在 pom.xml 中加入以下依赖即可:

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

PageHelper 的使用也非常简单,只需要在查询之前调 PageHelper.startPage() 方法即可开始分页。例如:

// 开始分页PageHelper.startPage(pageNum, pageSize);// 查询List<User> userList = userService.getUserList();// 封装分页对象PageInfo<User> pageInfo = new PageInfo<>(userList);

其中,pageNum 表示要查询的页码,pageSize 表示每页的记录数。调用 startPage 方法之后,PageHelper 会自动将下一次查询作为分页查询,并且会在查询之后返回一个 Page 对象,然后可以将这个对象转换为 PageInfo 对象,从而获得分页相关的信息。

这里其实存在两个问题:

  1. 为什么查询之后会返回 Page 对象,而不是 List 对象?
  2. 为什么不直接将 list 返回,而是需要封装一次再返回PageInfo 对象?

这里我们稍后再回答,先继续说明一些 PageHelper 的一些使用技巧。

Page page = PageHelper.startPage(pageNum, pageSize, true); - true 表示需要统计总数,这样会多进行一次请求 select count(0),不传默认为 true。

1)统计总数(将SQL语句变为 select count(0) from xxx,只对简单SQL语句其效果,复杂SQL语句需要自己写)

    Page<?> page = PageHelper.startPage(1,-1);
    long count = page.getTotal();

2)使用PageHelper查全部(不分页)

    PageHelper.startPage(1,0);
    List<?> allList = queryForList( xxx.class , "queryAll" , param);

2.PageHelper的底层原理

首先调用 PageHelper 的 startPage 方法开启分页,方法中会将分页参数存到一个变量 ThreadLocal<Page> LOCAL_PAGE中;

然后调用 mapper 进行查询,这里实际上会被 PageInterceptor 类拦截,执行其重写的 interceptor 方法,该方法中主要做了以下两件事:

  • 获取到 MappedStatement,拿到业务写好的 sql,将 sql 改造成 select count(0) 并执行查询,并将执行结果存到 LOCAL_PAGE 里的Page 中的 total 属性,表示总条数
  • 获取到 xml 中的 sql 语句,并 append 一些分页 sql 段,然后执行,将执行结果存到 LOCAL_PAGE 里的 Page 中的 list 属性,这里的Page 类实际是 ArrayList 的子类。

可以看出,结果是封装到了 Page 中,最后交由 PageInfo,从中可以获取到总条数、总页数等参数。

1.分页参数储存

首先看PageHelper.startPage的源码:

 public static <E> Page<E> startPage(int pageNum, int pageSize, boolean count, Boolean reasonable, Boolean pageSizeZero) {
 Page<E> page = new Page(pageNum, pageSize, count);
        page.setReasonable(reasonable);
        page.setPageSizeZero(pageSizeZero);
 Page<E> oldPage = getLocalPage();
 if (oldPage != null && oldPage.isOrderByOnly()) {
            page.setOrderBy(oldPage.getOrderBy());
 }
 setLocalPage(page);
 return page;
 }

其实主要就是把分页参数给到 Page ,然后将实例 Page 存储到 ThreadLocal 中。

public abstract class PageMethod {
 protected static final ThreadLocal<Page> LOCAL_PAGE = new ThreadLocal();
 public PageMethod() {
 }
 protected static void setLocalPage(Page page) {
 LOCAL_PAGE.set(page);
 }}

继续看执行SQL是怎么做的

2.拦截器改造 SQL

(1)统计总数

PageHelper 是通过拦截器底层执行 sql,对应的拦截器是 PageInterceptor,首先来看看这个类头部的定义,可以看出拦截了 Executor 的 query方法,毕竟 Mybatis 底层查询实际是借助 SqlSeesion 调用 Executor#query。

​
@Intercepts({@Signature( type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}), @Signature( type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class})})public class PageInterceptor implements Interceptor { private static final Log log = LogFactory.getLog(PageInterceptor.class); private static boolean debug = false; protected Cache<String, MappedStatement> msCountMap = null; protected CountMsIdGen countMsIdGen; private volatile Dialect dialect; private String countSuffix; private String default_dialect_class;

 

然后重点看下 intercept 方法,方法中传入的 Invocation 是JDK进行动态代理的时候,Plugin 类将反射信息封装到 Invocation 里,然后传给 intercept。

看下在哪里进行了总条数查询,进入 count 方法内看下:

继续追踪代码执行过程,发现进入 executeAutoCount 方法内,这个方法内有个变量为 countSql,其内容正是"select count(0)...",说明 PageHelper 在此处进行了总条数查询。

(2)分页查询

再看下 intercept 方法中如何进行如何分页查询:

在 pageQuery 方法中进行实际查询操作:

方法中的 pageSql 即为分页查询语句,看下 getPageSql 是如何实现的:

getPageSql 这个方法会根据不同的数据库,对 sql 进行不同的改造,这里关注下 MySql 是如何改造的:

很明显到这里就能看出 PageHelper 在针对分页查询时对每一个查询 sql 末尾都增加了 limit 子句,最大的一个问题得到了解决,代码后续就是拼接后Sql 的执行返回过程。值得注意的是,在 intercept 方法末尾的 finally 中调用 afferAll 方法对 ThreadLocal 进行 remove。

3.PageInfo

实际代码中进行分页查询得到 list之后,还要将其封装进 PageInfo 类中,才能获取到分页信息。我们关注下 PageInfo 中的构造器:

在这段代码中,将list强转为Page,再看下Page类中的设计,Page 类实际上是 ArrayList 的子类,且 Page 类中包含了分页的具体信息,而分页查询返回的 list 实际类型就是 Page,所以将其封装为 PageInfo 再返回也是合理且正确的。

2.安全性问题

PageHelper 的 startPage 方法使用了静态的 ThreadLocal 参数,分页参数和线程是绑定的。 只要保证在 startPage 方法调用后紧跟 MyBatis 查询方法,这就是安全的。因为 PageHelper 在 finally 代码段中自动清除了 ThreadLocal 存储的对象。但是例如下面这样的代码,就是不安全的用法:

PageHelper.startPage(1, 10);List<Country> list;if (param1 != null) {
    list = countryMapper.selectIf(param1);} else {
    list = new ArrayList<Country>();}

这种情况下由于 param1 存在 null 的情况,就会导致 PageHelper 生产了一个分页参数,但是没有被消费,这个参数就会一直保留在这个线程上。当这个线程再次被使用时,就可能导致不该分页的方法去消费这个分页参数,这就产生了莫名其妙的分页。

上面这个代码,应该写成下面这个样子:

List<Country> list;if (param1 != null) {
 PageHelper.startPage(1, 10);
    list = countryMapper.selectIf(param1);} else {
    list = new ArrayList<Country>();}

这种写法就能保证安全。当然也可以手动清理 ThreadLocal 存储的分页参数,如下代码所示,但是这种写法不好看,且没有必要,推荐上面的写法。

List<Country> list;if (param1 != null) {
 PageHelper.startPage(1, 10);
 try {
        list = countryMapper.selectAll();
 } finally {
 PageHelper.clearPage();
 }} else {
    list = new ArrayList<Country>();}

 二.键集分页

Spring Boot中实现“键集分页”(Keyset Pagination)算法,主要是利用数据库的排序功能,通过记录上一次查询结果的最后一条记录的排序键(通常是主键或者唯一键),在后续的查询中利用这个排序键来获取下一页的数据。这样可以避免分页查询中的“跳页”问题,尤其是在有新数据插入时。

以下是一个基于Spring Data JPA实现键集分页的示例:

定义实体类: 假设我们有一个Post实体,它有一个主键id和一个用于排序的字段createdDate

@Entity
public class Post {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String title;

    @Temporal(TemporalType.TIMESTAMP)
    private Date createdDate;

    // 省略getter和setter
}

 

定义存储库接口: 创建一个继承JpaRepository的存储库接口,并添加一个支持键集分页的方法。

public interface PostRepository extends JpaRepository<Post, Long> {

    @Query("SELECT p FROM Post p WHERE p.createdDate > :lastCreatedDate ORDER BY p.createdDate ASC")
    List<Post> findPostsAfterDate(@Param("lastCreatedDate") Date lastCreatedDate, Pageable pageable);
}

 

实现服务层: 在服务层中,我们需要一个方法来处理键集分页逻辑。

@Service
public class PostService {

    @Autowired
    private PostRepository postRepository;

    public Page<Post> getKeysetPage(Date lastCreatedDate, Pageable pageable) {
        // 获取分页数据
        List<Post> posts = postRepository.findPostsAfterDate(lastCreatedDate, pageable);

        // 获取总记录数,这里需要根据实际情况从数据库中查询
        long total = postRepository.countByCreatedDateGreaterThan(lastCreatedDate);

        // 创建Page对象
        return new PageImpl<>(posts, pageable, total);
    }
}

 

实现控制器: 在控制器中,我们需要处理分页请求,并提供必要的参数来调用服务层的getKeysetPage方法。

@RestController
@RequestMapping("/posts")
public class PostController {

    @Autowired
    private PostService postService;

    @GetMapping
    public Page<Post> getPosts(@RequestParam(value = "lastCreatedDate", required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) Date lastCreatedDate,
                               @RequestParam(value = "page", defaultValue = "0") int page,
                               @RequestParam(value = "size", defaultValue = "10") int size) {
        Pageable pageable = PageRequest.of(page, size);
        return postService.getKeysetPage(lastCreatedDate, pageable);
    }
}

在这个例子中,我们使用createdDate字段作为排序键。客户端在请求分页数据时,需要提供上一次查询结果的最后一条记录的createdDate。服务器端使用这个日期来查询下一页的数据。

需要注意的是,键集分页算法适用于排序字段具有唯一性的情况,例如时间戳或者自增的主键。如果排序字段不具有唯一性,可能需要结合其他字段来确保分页的准确性。此外,这种算法可能不适用于随机访问或者跳转到特定页码的操作。

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

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

相关文章

pin脚的缺陷检测

忍不住 我才是最大的缺陷首先应该学好表达头脑风暴分割paddledetection小目标检测也不行缺陷检测1.缺陷标注修改代码为自己的数据集训练训练结果结果图片 结论再次出发 我才是最大的缺陷 真的&#xff0c;我真的被整无语了。测测测测&#xff0c;测个鬼。一天天的净整些没用的…

隐蔽处工程监管系统

随着科技的飞速发展&#xff0c;信息化、智能化已经成为各行各业发展的必然趋势。在工程建设领域&#xff0c;传统的监管方式已经难以满足现代工程管理的需求。为了提高工程监管的效率和精度&#xff0c;信鸥科技倾力打造了一款全新的工程监管系统&#xff0c;为工程建设行业带…

14:有效的符号

给定一个只包括 (&#xff0c;)&#xff0c;{&#xff0c;}&#xff0c;[&#xff0c;] 的字符串 s &#xff0c;判断字符串是否有效。 有效字符串需满足&#xff1a; 左括号必须用相同类型的右括号闭合。左括号必须以正确的顺序闭合。每个右括号都有一个对应的相同类型的左括…

XSS学习(cookie远程登录演示)

1.HTTP特点&#xff1a; 1.请求应答模式。 2.灵活可扩展 3.可靠传输 4.无状态。 这里给大家举一个例子&#xff1a; HTTP是无状态的&#xff0c;所按理来说我每进行一次会话&#xff0c;比如我在CSDN发一个帖子&#xff0c;好像按理来以说我都要进行一次重新登陆&#xff0…

3.4 CSS取值与单位

3.4.1 数字 数字取值是在CSS2中规定的&#xff0c;有三种取值形式如表3-3所示。 3.4.2 长度 长度取值<length>是在CSS2中规定的&#xff0c;表示方法为数值接长度单位。可用于描述文本、图像或其他各类元素的尺寸。 长度取值的单位可分为相对长度单位和绝对长度单位。相…

day5-QT

widget.h #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include<QFontDialog> //字体对话框类 #include<QFont> //字体类 #include<QMessageBox> //消息对话框类 #include<QColorDialog> //颜色对话框类 #include<QColor> //颜…

静态路由表学习实验

实验要求&#xff1a;各个pc设备可以通信&#xff0c;并且可以访问外网&#xff0c;假设R1已连接外网 拓扑结构 思路&#xff1a;配置pc机ip地址&#xff0c;子网掩码&#xff0c;和网关&#xff08;网关地址是上层路由接口的地址&#xff09;&#xff0c;配置路由各个接口地址…

SpringBoot整合Swagger-UI实现在线API文档

✅作者简介:大家好,我是Leo,热爱Java后端开发者,一个想要与大家共同进步的男人😉😉🍎个人主页:Leo的博客 💞当前专栏: 循序渐进学SpringBoot ✨特色专栏: MySQL学习 🥭本文内容:SpringBoot整合Swagger-UI实现在线API文档 📚个人知识库: Leo知识库,欢迎大…

STM32学习笔记(6_7)- TIM定时器的编码器接口原理

无人问津也好&#xff0c;技不如人也罢&#xff0c;都应静下心来&#xff0c;去做该做的事。 最近在学STM32&#xff0c;所以也开贴记录一下主要内容&#xff0c;省的过目即忘。视频教程为江科大&#xff08;改名江协科技&#xff09;&#xff0c;网站jiangxiekeji.com 现在开…

“Linux 三剑客”,通常指的是三个经典的命令行工具:grep、sed 和 awk

1、grep&#xff1a; 简介&#xff1a;grep 是一个强大的文本搜索工具&#xff0c;可以用于在文件中查找匹配特定模式的行。示例&#xff1a; 搜索包含特定关键词的行&#xff1a; grep "keyword" filename 递归搜索目录下所有文件&#xff1a; grep -r define zj…

聊聊多模态大模型处理的思考

多模态&#xff1a;文本、音频、视频、图像等多形态的展现形式。目前部门内业务要求领域大模型需要是多模态——支持音频/文本。从个人思考的角度来审视下&#xff0c;审视下多模态大模型的实现方式。首先就要区分输入与输出&#xff0c;即输入的模态与输出的模态。从目前来看&…

专项测试之「 性能测试」总结

1、性能测试概念 虚拟用户】模拟真实业务逻辑步骤的虚拟用户&#xff0c;其模拟的操作步骤都被记录再虚拟用户脚本中。 【事务】事务是性能测试脚本的一个重要特性&#xff0c;按照最小的http请求打包而成。 【TPS】每秒中系统处理的交易或者事务的数量。 【PV】用户浏览器…

SQLynx发布3.0.0版本:带来更流畅便捷的SQL开发体验

作为新一代的一站式数据库管理开发工具&#xff0c; SQLynx自发布上线以来&#xff0c;一直受到广大用户的好评与鼓励。 为了给用户提供更高效、更便捷、更可靠的数据库管理开发体验&#xff0c;SQLynx今日正式发布3.0.0版本&#xff0c;同步在麦聪软件官网上线&#xff0c;全…

大型网络游戏设计与AI赋能-3

接上文&#xff01;&#xff01;&#xff01;&#xff01; 先和大家互动一下 大家觉得架构设计包含了哪些东西&#xff1f; 大家可能会提起一些名词&#xff0c;比如框架、不同功能、工具集、软件体系结构、设计思想等。其实引擎是一种软件。我们说传统的软件设计的这个体系里…

搭建机器人产业发展重要展示平台“2024南京国际机器人展览会”

2024南京国际智能机器人展览会 2024 Nanjing Intelligent Robot Expo 时间:2024年11月22-24日 地点:南京国际博览中心 南京&#xff0c;这座历史悠久的文化名城&#xff0c;如今正站在机器人产业发展的前沿。随着全球科技的飞速进步&#xff0c;机器人产业已经成为推动经济社…

数据结构·二叉树(2)

目录 1 堆的概念 2 堆的实现 2.1 堆的初始化和销毁 2.2 获取堆顶数据和堆的判空 2.3 堆的向上调整算法 2.4 堆的向下调整算法 2.4 堆的插入 2.5 删除堆顶数据 2.6 建堆 3 建堆的时间复杂度 3.1 向上建堆的时间复杂度 3.2向下建堆的时间复杂度 4 堆的排序 前言&…

【C++语言】冲突-C语言:命名冲突(输入输出、缺省参数、引用、内联函数)

文章目录 前言正文2. C的输入与输出&#xff1a;3.缺省参数3.1 缺省参数的概念&#xff1a;3.2 缺省参数的分类&#xff1a;全缺省参数&#xff1a;半缺省参数&#xff1a; 4.函数重载4.1 函数重载的概念&#xff1a; 5.引用5.1 引用的基本概念&#xff1a;5.2 引用的特性&…

后端代码1

// 新增 public JsonResultVo<?> create(ApiIgnore RequestAttribute(ConstVal.REQ_USER) BaseUser baseUser,RequestBody IUTradeBuyPreserveVo iuTradeBuyPreserveVo) {//权限判断if (!baseCompanyService.dataPermission(baseUser, iuTradeBuyPreserveVo.getCompanyi…

Kimi和ChatGPT做古诗词阅读理解,谁更胜一筹?

前几天发过一篇Kimi整理会议的体验教程&#xff0c;没想到大家很感兴趣&#xff0c;这次再来拿Kimi做古诗词阅读理解看看&#xff0c;同时也对比下ChatGPT的效果。 ChatGPT是几乎家喻户晓的AI大模型&#xff0c;Kimi和它对比有哪些异同点呢&#xff1f; 首先它们都是基于对话…

【小沐学AI】智谱AI大模型的一点点学习(Python)

文章目录 1、简介1.1 大模型排行榜 2、智谱AI2.1 GLM2.1.1 模型简介2.1.2 开源代码2.1.2.1 GLM-130B 2.2 ChatGLM2.2.1 模型简介2.2.2 开源代码2.2.2.1 ChatGLM2.2.2.2 ChatGLM22.2.2.3 ChatGLM3 2.3 CodeGeeX2.3.1 模型简介2.3.2 开源代码 2.4 CogView2.4.1 模型简介2.4.2 开源…