当前位置: 首页 > news >正文

分页优化之——游标分页

游标分页(Cursor-based Pagination) 是一种高效的分页方式,特别适用于大数据集和无限滚动的场景。与传统的基于页码的分页(如 page=1&size=10)不同,游标分页通过一个唯一的游标(通常是时间戳或唯一 ID)来标记分页的位置,避免了传统分页在数据变动时的重复或遗漏问题。

以下是游标分页在前后端的实现方式:


1. 游标分页的核心概念

  1. 游标(Cursor)

    • 游标是一个唯一标识符,通常是数据的某个字段(如 id 或 created_at)。

    • 游标用于标记分页的起始位置。

  2. 分页方向

    • 向前分页(Next Page):获取游标之后的记录。

    • 向后分页(Previous Page):获取游标之前的记录。

  3. 响应结构

    • 返回分页数据时,需要包含下一个游标和上一个游标,以便客户端继续分页。


2. 后端实现

2.1 数据库查询

假设数据表结构如下:

CREATE TABLE posts (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    content TEXT,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
  • 向前分页:获取 id > cursor 的记录。

  • 向后分页:获取 id < cursor 的记录。

2.2 后端 API 设计
  • 请求参数

    • cursor:当前游标(可选,首次请求时可以为空)。

    • limit:每页的记录数。

    • direction:分页方向(next 或 prev,可选)。

  • 响应结构

    {
        "data": [],         // 当前页的数据
        "next_cursor": "123", // 下一页的游标
        "prev_cursor": "456"  // 上一页的游标
    }

2.3 后端代码实现(Java + Spring Boot)
@RestController
@RequestMapping("/posts")
public class PostController {

    @Autowired
    private PostRepository postRepository;

    @GetMapping
    public ResponseEntity<CursorPageResponse<Post>> getPosts(
            @RequestParam(required = false) Long cursor,
            @RequestParam(defaultValue = "10") int limit,
            @RequestParam(defaultValue = "next") String direction) {

        List<Post> posts;
        Long nextCursor = null;
        Long prevCursor = null;

        if ("next".equals(direction)) {
            // 向前分页:获取 id > cursor 的记录
            posts = postRepository.findByIdGreaterThan(cursor, PageRequest.of(0, limit));
            if (!posts.isEmpty()) {
                nextCursor = posts.get(posts.size() - 1).getId();
                prevCursor = posts.get(0).getId();
            }
        } else if ("prev".equals(direction)) {
            // 向后分页:获取 id < cursor 的记录
            posts = postRepository.findByIdLessThan(cursor, PageRequest.of(0, limit));
            if (!posts.isEmpty()) {
                nextCursor = posts.get(0).getId();
                prevCursor = posts.get(posts.size() - 1).getId();
            }
        } else {
            // 首次请求,获取最新的记录
            posts = postRepository.findLatest(PageRequest.of(0, limit));
            if (!posts.isEmpty()) {
                nextCursor = posts.get(posts.size() - 1).getId();
            }
        }

        CursorPageResponse<Post> response = new CursorPageResponse<>();
        response.setData(posts);
        response.setNextCursor(nextCursor);
        response.setPrevCursor(prevCursor);

        return ResponseEntity.ok(response);
    }
}

3. 前端实现

3.1 首次加载
  • 首次加载时,不传递 cursor,获取最新的数据。

fetch('/posts?limit=10')
    .then(response => response.json())
    .then(data => {
        console.log(data);
        // 渲染数据
        // 保存 next_cursor 和 prev_cursor
    });
3.2 加载下一页
  • 使用 next_cursor 请求下一页数据。

fetch(`/posts?cursor=${nextCursor}&limit=10&direction=next`)
    .then(response => response.json())
    .then(data => {
        console.log(data);
        // 渲染数据
        // 更新 next_cursor 和 prev_cursor
    });
3.3 加载上一页
  • 使用 prev_cursor 请求上一页数据。

fetch(`/posts?cursor=${prevCursor}&limit=10&direction=prev`)
    .then(response => response.json())
    .then(data => {
        console.log(data);
        // 渲染数据
        // 更新 next_cursor 和 prev_cursor
    });

4. 游标分页的优点

  1. 高效

    • 基于游标的分页可以利用索引,查询性能更高。

  2. 稳定性

    • 数据变动时(如新增或删除记录),游标分页不会出现重复或遗漏问题。

  3. 适合无限滚动

    • 无限滚动场景下,游标分页比传统分页更自然。


5. 游标分页的缺点

  1. 不支持随机跳页

    • 游标分页只能按顺序加载下一页或上一页,无法直接跳转到指定页码。

  2. 实现复杂度较高

    • 需要前后端协同设计游标逻辑。


6. 总结

  • 游标分页 是一种高效且稳定的分页方式,特别适合大数据集和无限滚动场景。

  • 后端通过游标(如 id 或 created_at)实现分页查询,并返回 next_cursor 和 prev_cursor

  • 前端根据游标加载下一页或上一页数据。

  • 与传统分页相比,游标分页更适合动态数据场景,但无法支持随机跳页。

通过以上实现,可以高效地处理大数据集的分页需求,同时避免传统分页的常见问题。

相关文章:

  • IREE 内存分配算法概述
  • 深入理解MySQL中的MVCC机制
  • 双一流软件工程大二听闻 Java 前景堪忧,是否该转C++或人工智能或者读研?
  • 数据驱动的业务智能与决策支持:从数据到智慧的进化之路
  • JDBC 操作 BLOB(二进制大对象)和 CLOB(字符大对象)的完整示例代码,包含 插入、读取 操作及详细注释
  • RocketMQ面试题:基础部分
  • G-Star 校园开发者计划·黑科大|开源第一课之 Git 入门
  • 简易shell
  • Python深浅拷贝
  • mysql 查询进程查看并释放
  • 存储过程在高并发环境下的重要性
  • await func().catch()和try{ func() }.catch(),两种写法,有什么区别
  • 设计模式之工厂模式的优缺点
  • NLP 与常见的nlp应用
  • AI在工业自动化中的应用与挑战
  • 麦科信新品发布,8MHz,300Arms高频交直流电流探头CP3008
  • Linux驱动开发实战之SRIO驱动(二)基于Tsi721驱动
  • 分布式中间件:RabbitMQ确认消费机制
  • QT网页显示的几种方法及对比
  • 计算机网络精讲day1——计算机网络的性能指标(上)
  • 金融监管局:已设立74支私募股权投资基金,支持投资科技创新企业
  • 印对巴军事打击后,巴外交部召见印度驻巴临时代办
  • 一揽子十条货币政策措施出炉:降准降息,设立五千亿服务消费与养老再贷款
  • 阿曼宣布美国与胡塞武装达成停火协议
  • 有人悬赏十万寻找“全国仅剩1只”的斑鳖,发帖者回应并证实
  • 郑州一街道被指摊贩混乱经营,12345热线:已整治并加强巡查