分页优化之——游标分页

游标分页(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

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

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

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

相关推荐
北漂老男孩33 分钟前
IntelliJ IDEA 调试技巧指南
java·ide·intellij-idea
八股文领域大手子1 小时前
Leetcode32 最长有效括号深度解析
java·数据库·redis·sql·mysql
上官美丽1 小时前
Springboot中的@ConditionalOnBean注解:使用指南与最佳实践
java·spring boot·mybatis
Another Iso2 小时前
Windows安装Apache Maven 3.9.9
java·maven
鹏神丶明月天2 小时前
mybatis_plus的乐观锁
java·开发语言·数据库
fantasy_42 小时前
Java数据类型 Arrays VS ArraysList VS LikedList 解析
java
IT__learning2 小时前
Java通过Apache POI操作Excel
java·apache·excel
ylfhpy2 小时前
Java面试黄金宝典1
java·开发语言·算法·面试·职场和发展
loveking63 小时前
SpringBoot实现发邮件功能+邮件内容带模版
java·spring boot·后端
liuyang___3 小时前
Spring boot+mybatis的批量删除
java·spring boot·mybatis