分页优化之——游标分页

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

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


1. 游标分页的核心概念

  1. 游标(Cursor)

    • 游标是一个唯一标识符,通常是数据的某个字段(如 idcreated_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:分页方向(nextprev,可选)。

  • 响应结构

    复制代码
    {
        "data": [],         // 当前页的数据
        "next_cursor": "123", // 下一页的游标
        "prev_cursor": "456"  // 上一页的游标
    }
2.3 后端代码实现(Java + Spring Boot)
java 复制代码
@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,获取最新的数据。
javascript 复制代码
fetch('/posts?limit=10')
    .then(response => response.json())
    .then(data => {
        console.log(data);
        // 渲染数据
        // 保存 next_cursor 和 prev_cursor
    });
3.2 加载下一页
  • 使用 next_cursor 请求下一页数据。
javascript 复制代码
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 请求上一页数据。
javascript 复制代码
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. 总结

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

  • 后端通过游标(如 idcreated_at)实现分页查询,并返回 next_cursorprev_cursor

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

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

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

相关推荐
怡人蝶梦7 分钟前
Java后端技术栈问题排查实战:Spring Boot启动慢、Redis缓存击穿与Kafka消费堆积
java·jvm·redis·kafka·springboot·prometheus
瓯雅爱分享12 分钟前
MES管理系统:Java+Vue,含源码与文档,实现生产过程实时监控、调度与优化,提升制造企业效能
java·mysql·vue·软件工程·源代码管理
鬼多不菜1 小时前
一篇学习CSS的笔记
java·前端·css
深色風信子1 小时前
Eclipse 插件开发 5.3 编辑器 监听输入
java·eclipse·编辑器·编辑器 监听输入·插件 监听输入
Blossom.1181 小时前
人工智能在智能健康监测中的创新应用与未来趋势
java·人工智能·深度学习·机器学习·语音识别
shangjg31 小时前
Kafka 如何保证不重复消费
java·分布式·后端·kafka
无处不在的海贼2 小时前
小明的Java面试奇遇之互联网保险系统架构与性能优化
java·面试·架构
Layux2 小时前
flowable候选人及候选人组(Candidate Users 、Candidate Groups)的应用包含拾取、归还、交接
java·数据库
Mylvzi2 小时前
Spring Boot 中 @RequestParam 和 @RequestPart 的区别详解(含实际项目案例)
java·spring boot·后端