分页并不是将所有数据查询出来后再分页,而是通过数据库的 LIMIT 语句直接从数据源中提取当前页的数据。MyBatis 分页插件(如 PageHelper) 的核心工作就是拦截原始 SQL 查询,并在 SQL 中加入分页逻辑(如 LIMIT 和 OFFSET),避免一次性加载所有数据到内存中。

分页的本质

分页的核心是在 SQL 查询层面加以限制,而不是在内存中对查询结果分页。例如:

假设有 10,000 条员工记录,而页面每次只显示 10 条:

  • 错误的分页方式:
    1. 查询所有 10,000 条记录到内存。
    2. 在内存中取出前 10 条,忽略剩余的 9,990 条。
    3. 问题 数据量大时,内存消耗严重,查询效率低下。
  • 正确的分页方式(数据库分页):
    1. 通过 SQL 的 LIMITOFFSET 限制查询结果。
    2. 只从数据库中读取 10 条记录,传递给前端。
    3. 效果: 数据库层面减少了无用数据的传输和加载,大幅提高性能。

MyBatis 分页插件的实现

分页插件(如 PageHelper)会自动拦截原始 SQL,并在其基础上动态追加 LIMITOFFSET 子句。例如:

  1. 原始 SQL:SELECT * FROM user;
  2. 分页插件自动追加:SELECT * FROM user LIMIT 10 OFFSET 20; -- 查询第 3 页(每页 10 条)

代码示例:

PageHelper.startPage(pageNum, pageSize);
List<User> users = userMapper.selectAll(); // 原始查询
PageInfo<User> pageInfo = new PageInfo<>(users);

这里的 startPage 方法会在 selectAll 执行前拦截 SQL 并附加分页参数。

避免一次性查询过多数据的措施

  1. 数据库层分页
    • 分页插件会直接在数据库层面执行分页 SQL,只加载当前页的数据到内存中,而不是加载全部数据。
    • 优点: 数据传输量和内存消耗仅与分页大小相关,与总数据量无关。
  2. 限制分页大小
    • 通过前端和后端约定,每页最多只能请求一定数量的数据。例如:
      • 每页数据量 pageSize 不超过 100 条,避免单次请求过多数据。

分页并不是将所有数据查询出来后再分页,而是通过数据库的 LIMIT 语句直接从数据源中提取当前页的数据。MyBatis 分页插件(如 PageHelper) 的核心工作就是拦截原始 SQL 查询,并在 SQL 中加入分页逻辑(如 LIMIT 和 OFFSET),避免一次性加载所有数据到内存中。


分页的本质

分页的核心是在 SQL 查询层面加以限制,而不是在内存中对查询结果分页。例如:

假设有 10,000 条员工记录,而页面每次只显示 10 条:

  • 错误的分页方式:
    1. 查询所有 10,000 条记录到内存。
    2. 在内存中取出前 10 条,忽略剩余的 9,990 条。
    3. 问题: 数据量大时,内存消耗严重,查询效率低下。
  • 正确的分页方式(数据库分页):
    1. 通过 SQL 的 LIMITOFFSET 限制查询结果。
    2. 只从数据库中读取 10 条记录,传递给前端。
    3. 效果: 数据库层面减少了无用数据的传输和加载,大幅提高性能。

SQL 示例:

sql复制代码SELECT * FROM user
LIMIT 10 OFFSET 0; -- 第 1 页
SELECT * FROM user
LIMIT 10 OFFSET 10; -- 第 2 页

MyBatis 分页插件的实现

分页插件(如 PageHelper)会自动拦截原始 SQL,并在其基础上动态追加 LIMITOFFSET 子句。例如:

  1. 原始 SQL:sql复制代码SELECT * FROM user;
  2. 分页插件自动追加:sql复制代码SELECT * FROM user LIMIT 10 OFFSET 20; -- 查询第 3 页(每页 10 条)

代码示例:

PageHelper.startPage(pageNum, pageSize);
List<User> users = userMapper.selectAll(); // 原始查询
PageInfo<User> pageInfo = new PageInfo<>(users);

这里的 startPage 方法会在 selectAll 执行前拦截 SQL 并附加分页参数。


避免一次性查询过多数据的措施

  1. 数据库层分页
    • 分页插件会直接在数据库层面执行分页 SQL,只加载当前页的数据到内存中,而不是加载全部数据。
    • 优点: 数据传输量和内存消耗仅与分页大小相关,与总数据量无关。
  2. 限制分页大小
    • 通过前端和后端约定,每页最多只能请求一定数量的数据。例如:
      • 每页数据量 pageSize 不超过 100 条,避免单次请求过多数据。
    • 示例:java复制代码if (pageSize > 100) { pageSize = 100; // 限制每页最大数据量 }
  3. 索引优化
    • 数据量大时,为分页查询的条件字段(如主键 id 或其他排序字段)建立索引。
    • 示例:sql复制代码CREATE INDEX idx_user_id ON user(id);
  4. 增量查询(滚动分页)
    • 当数据量极大时(如百万级),可使用滚动分页(基于游标),避免 OFFSET 导致的性能问题。
    • 示例:sql复制代码SELECT * FROM user WHERE id > 1000 LIMIT 10; -- 滚动分页

总结:MyBatis 分页避免一次性查询的关键点

  1. 分页 SQL 自动生成:
    • 分页插件通过拦截 SQL 自动加上 LIMITOFFSET 参数,直接在数据库层面分页。
  2. 只加载当前页的数据:
    • 数据库返回的数据量与分页大小相关,不会加载全部数据到内存。
  3. 避免过大的分页大小:
    • 限制每页的数据量,例如设置 pageSize <= 100
  4. 高效索引支持:
    • 确保查询条件字段(如 iddepartment)有索引,提升分页查询效率。

通过这些措施,分页查询不会一次性加载所有数据,而是高效地从数据库中提取当前页的数据,从而避免性能问题。

注意

前端在分页过程中,每次换页通常都需要向后端发送一个新的请求,以获取对应页的数据。这种方式称为 服务端分页(Server-side Pagination),它是处理大数据量时的最佳实践。

前端分页 vs 服务端分页

1. 前端分页(Client-side Pagination)

  • 特点:
    前端一次性加载所有数据到内存中,分页逻辑由前端实现(通过 JavaScript 操作数组进行切片)。
  • 优点:
    • 换页不需要向后端发请求,页面切换速度快。
  • 缺点:
    • 数据量大时(如 10,000 条),初始加载速度慢,前端内存占用大。
    • 数据的实时性差,无法动态获取后端更新的数据。

2. 服务端分页(Server-side Pagination)

  • 特点:
    前端每次换页都向后端发送请求,后端通过分页查询(LIMITOFFSET)返回当前页的数据。
  • 优点:
    • 数据量大时性能优异,初始加载快,前端内存占用小。
    • 支持数据的动态更新,每次获取最新数据。
  • 缺点:
    • 每次换页需要发送网络请求,受网络延迟影响。
    • 需要后端实现分页逻辑。

服务端分页前端也要分页展示

前端分页代码uni-app:

	data() {
		return {
			users: [], // 用户列表
			pageNum: 1, // 当前页码
			pageSize: 5, // 每页显示条数
			pages: 1, // 总页数
		};
	},
	methods: {
		// 获取分页数据
		getPage(page) {
			if (page < 1 || page > this.pages) return;

			uni.request({
				url: "http://localhost:8081/user/page",
				method: "GET",
				data: {
					pageNum: page,
					pageSize: this.pageSize,
				},
				success: (res) => {
					if (res.statusCode === 200) {
						const data = res.data;
						this.users = data.list; // 更新用户列表
						this.pageNum = data.pageNum; // 更新当前页码
						this.pages = data.pages; // 更新总页数
					} else {
						uni.showToast({
							title: "数据加载失败",
							icon: "none",
						});
					}
				},
				fail: () => {
					uni.showToast({
						title: "请求失败,请检查网络",
						icon: "none",
					});
				},
			});
		},

后端分页:

Controller层:

@GetMapping(value = "/page")
public ResponseEntity<PageInfo<User>> getUsersByPage(
        @RequestParam(value = "pageNum", defaultValue = "1") int pageNum,
        @RequestParam(value = "pageSize", defaultValue = "5") int pageSize) {
    PageInfo<User> pageInfo = userSerive.selectUsersByPage(pageNum, pageSize);
    if (pageInfo.getList().isEmpty()) {
        return ResponseEntity.status(HttpStatus.NO_CONTENT).body(null);
    }
    return ResponseEntity.ok(pageInfo);
}

Mapper层:
    // 分页查询
    List<User> selectByPage(@Param("offset") int offset, @Param("limit") int limit);

Categories:

Tags:

暂时没有回复

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注