分页是指将大量数据分割成多个小的数据块(页),每次只返回其中一页的数据,用户可以通过翻页来浏览所有数据。这样可以避免一次性加载过多数据导致的性能问题。
形象比喻:
想象分页就像阅读一本厚厚的书:
/**
* 分页核心参数
*/
public class PageParams {
// 当前页码(从1开始)
private Integer pageNum = 1;
// 每页显示数量
private Integer pageSize = 10;
// 总记录数
private Long total;
// 总页数
private Integer pages;
// 是否有下一页
private Boolean hasNext;
// 是否有上一页
private Boolean hasPrev;
}
PageHelper是MyBatis的分页插件,它通过拦截器机制在SQL执行前自动添加分页相关的SQL语句,实现物理分页。
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>3.5.3version>
dependency>
<dependency>
<groupId>com.github.pagehelpergroupId>
<artifactId>pagehelper-spring-boot-starterartifactId>
<version>1.4.6version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<scope>runtimescope>
dependency>
dependencies>
在application.yml
中配置PageHelper:
# 数据库配置
spring:
datasource:
url: jdbc:mysql://localhost:3306/test_db?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
# MyBatis配置
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.example.entity
# PageHelper分页插件配置
pagehelper:
# 指定数据库类型
helper-dialect: mysql
# 启用合理化,pageNum<=0时查询第一页,pageNum>总页数时查询最后一页
reasonable: true
# 支持通过Mapper接口参数来传递分页参数
support-methods-arguments: true
# 分页参数合理化,当pageNum<=0时查询第一页,pageNum>pages时查询最后一页
page-size-zero: false
# 为了支持startPage(Object params)方法,增加了该参数来配置参数映射
params: count=countSql
package com.example.entity;
import com.fasterxml.jackson.annotation.JsonFormat;
import java.time.LocalDateTime;
/**
* 用户实体类
*/
public class User {
private Long id;
private String username;
private String email;
private String phone;
private Integer age;
private String status;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTime;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime updateTime;
// 构造方法
public User() {}
public User(String username, String email, String phone, Integer age) {
this.username = username;
this.email = email;
this.phone = phone;
this.age = age;
this.status = "ACTIVE";
this.createTime = LocalDateTime.now();
this.updateTime = LocalDateTime.now();
}
// getter和setter方法
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public LocalDateTime getCreateTime() {
return createTime;
}
public void setCreateTime(LocalDateTime createTime) {
this.createTime = createTime;
}
public LocalDateTime getUpdateTime() {
return updateTime;
}
public void setUpdateTime(LocalDateTime updateTime) {
this.updateTime = updateTime;
}
}
package com.example.mapper;
import com.example.entity.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 用户Mapper接口
*/
@Mapper
public interface UserMapper {
/**
* 查询所有用户
*/
List<User> selectAllUsers();
/**
* 根据条件查询用户
*/
List<User> selectUsersByCondition(@Param("username") String username,
@Param("email") String email,
@Param("status") String status);
/**
* 根据年龄范围查询用户
*/
List<User> selectUsersByAgeRange(@Param("minAge") Integer minAge,
@Param("maxAge") Integer maxAge);
/**
* 根据ID查询用户
*/
User selectUserById(@Param("id") Long id);
/**
* 插入用户
*/
int insertUser(User user);
/**
* 更新用户
*/
int updateUser(User user);
/**
* 删除用户
*/
int deleteUserById(@Param("id") Long id);
}
在src/main/resources/mapper/UserMapper.xml
中:
DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.UserMapper">
<resultMap id="UserResultMap" type="com.example.entity.User">
<id column="id" property="id"/>
<result column="username" property="username"/>
<result column="email" property="email"/>
<result column="phone" property="phone"/>
<result column="age" property="age"/>
<result column="status" property="status"/>
<result column="create_time" property="createTime"/>
<result column="update_time" property="updateTime"/>
resultMap>
<select id="selectAllUsers" resultMap="UserResultMap">
SELECT id, username, email, phone, age, status, create_time, update_time
FROM users
ORDER BY create_time DESC
select>
<select id="selectUsersByCondition" resultMap="UserResultMap">
SELECT id, username, email, phone, age, status, create_time, update_time
FROM users
<where>
<if test="username != null and username != ''">
AND username LIKE CONCAT('%', #{username}, '%')
if>
<if test="email != null and email != ''">
AND email LIKE CONCAT('%', #{email}, '%')
if>
<if test="status != null and status != ''">
AND status = #{status}
if>
where>
ORDER BY create_time DESC
select>
<select id="selectUsersByAgeRange" resultMap="UserResultMap">
SELECT id, username, email, phone, age, status, create_time, update_time
FROM users
<where>
<if test="minAge != null">
AND age >= #{minAge}
if>
<if test="maxAge != null">
AND age <= #{maxAge}
if>
where>
ORDER BY age ASC
select>
<select id="selectUserById" resultMap="UserResultMap">
SELECT id, username, email, phone, age, status, create_time, update_time
FROM users
WHERE id = #{id}
select>
<insert id="insertUser" parameterType="com.example.entity.User" useGeneratedKeys="true" keyProperty="id">
INSERT INTO users (username, email, phone, age, status, create_time, update_time)
VALUES (#{username}, #{email}, #{phone}, #{age}, #{status}, #{createTime}, #{updateTime})
insert>
<update id="updateUser" parameterType="com.example.entity.User">
UPDATE users SET
username = #{username},
email = #{email},
phone = #{phone},
age = #{age},
status = #{status},
update_time = #{updateTime}
WHERE id = #{id}
update>
<delete id="deleteUserById">
DELETE FROM users WHERE id = #{id}
delete>
mapper>
package com.example.dto;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
/**
* 分页请求参数基类
*/
public class PageRequest {
/**
* 当前页码,从1开始
*/
@Min(value = 1, message = "页码不能小于1")
private Integer pageNum = 1;
/**
* 每页显示数量
*/
@Min(value = 1, message = "每页显示数量不能小于1")
@Max(value = 100, message = "每页显示数量不能大于100")
private Integer pageSize = 10;
/**
* 排序字段
*/
private String orderBy;
/**
* 排序方向:ASC(升序)、DESC(降序)
*/
private String sortOrder = "DESC";
public PageRequest() {}
public PageRequest(Integer pageNum, Integer pageSize) {
this.pageNum = pageNum;
this.pageSize = pageSize;
}
// getter和setter方法
public Integer getPageNum() {
return pageNum;
}
public void setPageNum(Integer pageNum) {
this.pageNum = pageNum;
}
public Integer getPageSize() {
return pageSize;
}
public void setPageSize(Integer pageSize) {
this.pageSize = pageSize;
}
public String getOrderBy() {
return orderBy;
}
public void setOrderBy(String orderBy) {
this.orderBy = orderBy;
}
public String getSortOrder() {
return sortOrder;
}
public void setSortOrder(String sortOrder) {
this.sortOrder = sortOrder;
}
}
package com.example.dto;
/**
* 用户查询请求参数
* 继承分页参数,添加用户特有的查询条件
*/
public class UserQueryRequest extends PageRequest {
/**
* 用户名(模糊查询)
*/
private String username;
/**
* 邮箱(模糊查询)
*/
private String email;
/**
* 手机号
*/
private String phone;
/**
* 状态
*/
private String status;
/**
* 最小年龄
*/
private Integer minAge;
/**
* 最大年龄
*/
private Integer maxAge;
/**
* 开始时间(创建时间范围查询)
*/
private String startTime;
/**
* 结束时间(创建时间范围查询)
*/
private String endTime;
public UserQueryRequest() {}
// getter和setter方法
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public Integer getMinAge() {
return minAge;
}
public void setMinAge(Integer minAge) {
this.minAge = minAge;
}
public Integer getMaxAge() {
return maxAge;
}
public void setMaxAge(Integer maxAge) {
this.maxAge = maxAge;
}
public String getStartTime() {
return startTime;
}
public void setStartTime(String startTime) {
this.startTime = startTime;
}
public String getEndTime() {
return endTime;
}
public void setEndTime(String endTime) {
this.endTime = endTime;
}
}
package com.example.common;
import com.github.pagehelper.PageInfo;
import java.util.List;
/**
* 分页响应结果封装
* @param 数据类型
*/
public class PageResult<T> {
/**
* 当前页码
*/
private Integer pageNum;
/**
* 每页显示数量
*/
private Integer pageSize;
/**
* 总记录数
*/
private Long total;
/**
* 总页数
*/
private Integer pages;
/**
* 当前页数据
*/
private List<T> list;
/**
* 是否有下一页
*/
private Boolean hasNext;
/**
* 是否有上一页
*/
private Boolean hasPrev;
/**
* 是否是第一页
*/
private Boolean isFirst;
/**
* 是否是最后一页
*/
private Boolean isLast;
public PageResult() {}
/**
* 基于PageInfo构造分页结果
*/
public PageResult(PageInfo<T> pageInfo) {
this.pageNum = pageInfo.getPageNum();
this.pageSize = pageInfo.getPageSize();
this.total = pageInfo.getTotal();
this.pages = pageInfo.getPages();
this.list = pageInfo.getList();
this.hasNext = pageInfo.isHasNextPage();
this.hasPrev = pageInfo.isHasPreviousPage();
this.isFirst = pageInfo.isIsFirstPage();
this.isLast = pageInfo.isIsLastPage();
}
/**
* 创建分页结果的静态方法
*/
public static <T> PageResult<T> of(PageInfo<T> pageInfo) {
return new PageResult<>(pageInfo);
}
/**
* 创建空的分页结果
*/
public static <T> PageResult<T> empty() {
PageResult<T> result = new PageResult<>();
result.pageNum = 1;
result.pageSize = 10;
result.total = 0L;
result.pages = 0;
result.list = List.of();
result.hasNext = false;
result.hasPrev = false;
result.isFirst = true;
result.isLast = true;
return result;
}
// getter和setter方法
public Integer getPageNum() {
return pageNum;
}
public void setPageNum(Integer pageNum) {
this.pageNum = pageNum;
}
public Integer getPageSize() {
return pageSize;
}
public void setPageSize(Integer pageSize) {
this.pageSize = pageSize;
}
public Long getTotal() {
return total;
}
public void setTotal(Long total) {
this.total = total;
}
public Integer getPages() {
return pages;
}
public void setPages(Integer pages) {
this.pages = pages;
}
public List<T> getList() {
return list;
}
public void setList(List<T> list) {
this.list = list;
}
public Boolean getHasNext() {
return hasNext;
}
public void setHasNext(Boolean hasNext) {
this.hasNext = hasNext;
}
public Boolean getHasPrev() {
return hasPrev;
}
public void setHasPrev(Boolean hasPrev) {
this.hasPrev = hasPrev;
}
public Boolean getIsFirst() {
return isFirst;
}
public void setIsFirst(Boolean isFirst) {
this.isFirst = isFirst;
}
public Boolean getIsLast() {
return isLast;
}
public void setIsLast(Boolean isLast) {
this.isLast = isLast;
}
}
package com.example.service;
import com.example.dto.UserQueryRequest;
import com.example.entity.User;
import com.example.common.PageResult;
/**
* 用户服务接口
*/
public interface UserService {
/**
* 分页查询所有用户
*/
PageResult<User> getAllUsers(UserQueryRequest request);
/**
* 根据条件分页查询用户
*/
PageResult<User> getUsersByCondition(UserQueryRequest request);
/**
* 根据年龄范围分页查询用户
*/
PageResult<User> getUsersByAgeRange(Integer minAge, Integer maxAge,
Integer pageNum, Integer pageSize);
/**
* 根据ID查询用户
*/
User getUserById(Long id);
/**
* 创建用户
*/
User createUser(User user);
/**
* 更新用户
*/
User updateUser(User user);
/**
* 删除用户
*/
boolean deleteUser(Long id);
/**
* 批量删除用户
*/
boolean deleteUsers(List<Long> ids);
}
package com.example.service.impl;
import com.example.dto.UserQueryRequest;
import com.example.entity.User;
import com.example.mapper.UserMapper;
import com.example.service.UserService;
import com.example.common.PageResult;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.time.LocalDateTime;
import java.util.List;
/**
* 用户服务实现类
*/
@Service
public class UserServiceImpl implements UserService {
private static final Logger logger = LoggerFactory.getLogger(UserServiceImpl.class);
@Autowired
private UserMapper userMapper;
/**
* 分页查询所有用户
*/
@Override
public PageResult<User> getAllUsers(UserQueryRequest request) {
logger.info("分页查询所有用户,页码:{},每页数量:{}", request.getPageNum(), request.getPageSize());
// 设置分页参数
PageHelper.startPage(request.getPageNum(), request.getPageSize());
// 设置排序
if (StringUtils.hasText(request.getOrderBy())) {
String orderBy = request.getOrderBy() + " " + request.getSortOrder();
PageHelper.orderBy(orderBy);
}
// 执行查询
List<User> users = userMapper.selectAllUsers();
// 创建分页信息
PageInfo<User> pageInfo = new PageInfo<>(users);
logger.info("查询完成,总记录数:{},总页数:{}", pageInfo.getTotal(), pageInfo.getPages());
return PageResult.of(pageInfo);
}
/**
* 根据条件分页查询用户
*/
@Override
public PageResult<User> getUsersByCondition(UserQueryRequest request) {
logger.info("根据条件分页查询用户,查询条件:{}", request);
// 设置分页参数
PageHelper.startPage(request.getPageNum(), request.getPageSize());
// 设置排序
if (StringUtils.hasText(request.getOrderBy())) {
String orderBy = request.getOrderBy() + " " + request.getSortOrder();
PageHelper.orderBy(orderBy);
}
// 执行条件查询
List<User> users = userMapper.selectUsersByCondition(
request.getUsername(),
request.getEmail(),
request.getStatus()
);
// 创建分页信息
PageInfo<User> pageInfo = new PageInfo<>(users);
logger.info("条件查询完成,总记录数:{},总页数:{}", pageInfo.getTotal(), pageInfo.getPages());
return PageResult.of(pageInfo);
}
/**
* 根据年龄范围分页查询用户
*/
@Override
public PageResult<User> getUsersByAgeRange(Integer minAge, Integer maxAge,
Integer pageNum, Integer pageSize) {
logger.info("根据年龄范围分页查询用户,年龄范围:{}-{},页码:{},每页数量:{}",
minAge, maxAge, pageNum, pageSize);
// 设置分页参数
PageHelper.startPage(pageNum, pageSize);
// 执行年龄范围查询
List<User> users = userMapper.selectUsersByAgeRange(minAge, maxAge);
// 创建分页信息
PageInfo<User> pageInfo = new PageInfo<>(users);
logger.info("年龄范围查询完成,总记录数:{},总页数:{}", pageInfo.getTotal(), pageInfo.getPages());
return PageResult.of(pageInfo);
}
/**
* 根据ID查询用户
*/
@Override
public User getUserById(Long id) {
logger.info("根据ID查询用户:{}", id);
User user = userMapper.selectUserById(id);
if (user == null) {
logger.warn("用户不存在,ID:{}", id);
} else {
logger.info("查询用户成功:{}", user.getUsername());
}
return user;
}
/**
* 创建用户
*/
@Override
public User createUser(User user) {
logger.info("创建用户:{}", user.getUsername());
// 设置创建时间和更新时间
user.setCreateTime(LocalDateTime.now());
user.setUpdateTime(LocalDateTime.now());
user.setStatus("ACTIVE");
// 插入用户
int result = userMapper.insertUser(user);
if (result > 0) {
logger.info("用户创建成功,ID:{}", user.getId());
return user;
} else {
logger.error("用户创建失败:{}", user.getUsername());
throw new RuntimeException("用户创建失败");
}
}
/**
* 更新用户
*/
@Override
public User updateUser(User user) {
logger.info("更新用户:{}", user.getId());
// 设置更新时间
user.setUpdateTime(LocalDateTime.now());
// 更新用户
int result = userMapper.updateUser(user);
if (result > 0) {
logger.info("用户更新成功,ID:{}", user.getId());
return user;
} else {
logger.error("用户更新失败,ID:{}", user.getId());
throw new RuntimeException("用户更新失败");
}
}
/**
* 删除用户
*/
@Override
public boolean deleteUser(Long id) {
logger.info("删除用户:{}", id);
int result = userMapper.deleteUserById(id);
if (result > 0) {
logger.info("用户删除成功,ID:{}", id);
return true;
} else {
logger.warn("用户删除失败,用户可能不存在,ID:{}", id);
return false;
}
}
/**
* 批量删除用户
*/
@Override
public boolean deleteUsers(List<Long> ids) {
logger.info("批量删除用户,数量:{}", ids.size());
int successCount = 0;
for (Long id : ids) {
if (deleteUser(id)) {
successCount++;
}
}
logger.info("批量删除完成,成功删除:{}/{}", successCount, ids.size());
return successCount == ids.size();
}
}
package com.example.controller;
import com.example.common.PageResult;
import com.example.common.Result;
import com.example.dto.UserQueryRequest;
import com.example.entity.User;
import com.example.service.UserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotEmpty;
import java.util.List;
/**
* 用户控制器
* 提供用户相关的分页接口
*/
@RestController
@RequestMapping("/api/users")
@Validated
public class UserController {
private static final Logger logger = LoggerFactory.getLogger(UserController.class);
@Autowired
private UserService userService;
/**
* 分页查询所有用户
* GET /api/users?pageNum=1&pageSize=10&orderBy=createTime&sortOrder=DESC
*/
@GetMapping
public Result<PageResult<User>> getAllUsers(@Valid UserQueryRequest request) {
logger.info("接收到分页查询请求:页码={}, 每页数量={}", request.getPageNum(), request.getPageSize());
PageResult<User> pageResult = userService.getAllUsers(request);
return Result.success("查询成功", pageResult);
}
/**
* 根据条件分页查询用户
* POST /api/users/search
*/
@PostMapping("/search")
public Result<PageResult<User>> searchUsers(@Valid @RequestBody UserQueryRequest request) {
logger.info("接收到条件查询请求:{}", request);
PageResult<User> pageResult = userService.getUsersByCondition(request);
return Result.success("查询成功", pageResult);
}
/**
* 根据年龄范围分页查询用户
* GET /api/users/age-range?minAge=18&maxAge=30&pageNum=1&pageSize=10
*/
@GetMapping("/age-range")
public Result<PageResult<User>> getUsersByAgeRange(
@RequestParam(required = false) Integer minAge,
@RequestParam(required = false) Integer maxAge,
@RequestParam(defaultValue = "1") @Min(value = 1, message = "页码不能小于1") Integer pageNum,
@RequestParam(defaultValue = "10") @Min(value = 1, message = "每页数量不能小于1") Integer pageSize) {
logger.info("根据年龄范围查询用户:{}-{}, 页码={}, 每页数量={}", minAge, maxAge, pageNum, pageSize);
PageResult<User> pageResult = userService.getUsersByAgeRange(minAge, maxAge, pageNum, pageSize);
return Result.success("查询成功", pageResult);
}
/**
* 根据ID查询单个用户
* GET /api/users/{id}
*/
@GetMapping("/{id}")
public Result<User> getUserById(@PathVariable @Min(value = 1, message = "用户ID必须大于0") Long id) {
logger.info("根据ID查询用户:{}", id);
User user = userService.getUserById(id);
if (user != null) {
return Result.success("查询成功", user);
} else {
return Result.error(404, "用户不存在");
}
}
/**
* 创建用户
* POST /api/users
*/
@PostMapping
public Result<User> createUser(@Valid @RequestBody User user) {
logger.info("创建用户:{}", user.getUsername());
User createdUser = userService.createUser(user);
return Result.success("用户创建成功", createdUser);
}
/**
* 更新用户
* PUT /api/users/{id}
*/
@PutMapping("/{id}")
public Result<User> updateUser(@PathVariable @Min(value = 1, message = "用户ID必须大于0") Long id,
@Valid @RequestBody User user) {
logger.info("更新用户:{}", id);
user.setId(id);
User updatedUser = userService.updateUser(user);
return Result.success("用户更新成功", updatedUser);
}
/**
* 删除单个用户
* DELETE /api/users/{id}
*/
@DeleteMapping("/{id}")
public Result<Void> deleteUser(@PathVariable @Min(value = 1, message = "用户ID必须大于0") Long id) {
logger.info("删除用户:{}", id);
boolean success = userService.deleteUser(id);
if (success) {
return Result.success("用户删除成功");
} else {
return Result.error(404, "用户不存在或删除失败");
}
}
/**
* 批量删除用户
* DELETE /api/users/batch
*/
@DeleteMapping("/batch")
public Result<Void> deleteUsers(@RequestBody @NotEmpty(message = "删除的用户ID列表不能为空") List<Long> ids) {
logger.info("批量删除用户,数量:{}", ids.size());
boolean success = userService.deleteUsers(ids);
if (success) {
return Result.success("批量删除成功");
} else {
return Result.error("批量删除失败,部分用户可能不存在");
}
}
}
1. 基础分页查询
# 查询第1页,每页10条记录
GET /api/users?pageNum=1&pageSize=10
# 查询第2页,每页20条记录,按创建时间倒序排列
GET /api/users?pageNum=2&pageSize=20&orderBy=createTime&sortOrder=DESC
# 查询第1页,每页5条记录,按年龄升序排列
GET /api/users?pageNum=1&pageSize=5&orderBy=age&sortOrder=ASC
2. 条件查询分页
# POST /api/users/search
Content-Type: application/json
{
"pageNum": 1,
"pageSize": 10,
"username": "张",
"status": "ACTIVE",
"minAge": 18,
"maxAge": 30,
"orderBy": "createTime",
"sortOrder": "DESC"
}
3. 年龄范围查询
# 查询18-30岁的用户,第1页,每页10条
GET /api/users/age-range?minAge=18&maxAge=30&pageNum=1&pageSize=10
{
"code": 200,
"message": "查询成功",
"success": true,
"timestamp": "2024-01-15 10:30:00",
"data": {
"pageNum": 1,
"pageSize": 10,
"total": 156,
"pages": 16,
"list": [
{
"id": 1,
"username": "张三",
"email": "[email protected]",
"phone": "13800138001",
"age": 25,
"status": "ACTIVE",
"createTime": "2024-01-15 10:00:00",
"updateTime": "2024-01-15 10:00:00"
},
{
"id": 2,
"username": "李四",
"email": "[email protected]",
"phone": "13800138002",
"age": 28,
"status": "ACTIVE",
"createTime": "2024-01-15 09:30:00",
"updateTime": "2024-01-15 09:30:00"
}
// ... 其他8条记录
],
"hasNext": true,
"hasPrev": false,
"isFirst": true,
"isLast": false
}
}
-- 创建数据库
CREATE DATABASE IF NOT EXISTS test_db
DEFAULT CHARACTER SET utf8mb4
DEFAULT COLLATE utf8mb4_unicode_ci;
USE test_db;
-- 创建用户表
CREATE TABLE users (
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '用户ID',
username VARCHAR(50) NOT NULL COMMENT '用户名',
email VARCHAR(100) NOT NULL COMMENT '邮箱',
phone VARCHAR(20) COMMENT '手机号',
age INT COMMENT '年龄',
status VARCHAR(20) DEFAULT 'ACTIVE' COMMENT '状态:ACTIVE-活跃,INACTIVE-非活跃',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
UNIQUE KEY uk_username (username),
UNIQUE KEY uk_email (email),
INDEX idx_age (age),
INDEX idx_status (status),
INDEX idx_create_time (create_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
-- 插入测试数据
INSERT INTO users (username, email, phone, age, status) VALUES
('张三', '[email protected]', '13800138001', 25, 'ACTIVE'),
('李四', '[email protected]', '13800138002', 28, 'ACTIVE'),
('王五', '[email protected]', '13800138003', 22, 'ACTIVE'),
('赵六', '[email protected]', '13800138004', 35, 'INACTIVE'),
('钱七', '[email protected]', '13800138005', 29, 'ACTIVE'),
('孙八', '[email protected]', '13800138006', 31, 'ACTIVE'),
('周九', '[email protected]', '13800138007', 27, 'ACTIVE'),
('吴十', '[email protected]', '13800138008', 24, 'INACTIVE'),
('郑十一', '[email protected]', '13800138009', 26, 'ACTIVE'),
('王十二', '[email protected]', '13800138010', 30, 'ACTIVE');
-- 批量插入更多测试数据(用于测试分页效果)
INSERT INTO users (username, email, phone, age, status)
SELECT
CONCAT('用户', LPAD((@row_number:=@row_number+1), 4, '0')) as username,
CONCAT('user', LPAD(@row_number, 4, '0'), '@example.com') as email,
CONCAT('138', LPAD(@row_number, 8, '0')) as phone,
18 + FLOOR(RAND() * 42) as age,
CASE WHEN RAND() > 0.2 THEN 'ACTIVE' ELSE 'INACTIVE' END as status
FROM
(SELECT @row_number:=10) r
CROSS JOIN
(SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5) t1
CROSS JOIN
(SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5) t2
CROSS JOIN
(SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5) t3
LIMIT 150;
创建一个通用的分页组件 Pagination.vue
:
共 {{ total }} 条记录,第 {{ currentPage }} / {{ totalPages }} 页
跳转到
页
每页显示
条
至
用户列表
加载中...
ID
用户名
邮箱
手机号
年龄
状态
创建时间
操作
{{ user.id }}
{{ user.username }}
{{ user.email }}
{{ user.phone }}
{{ user.age }}
{{ user.status === 'ACTIVE' ? '活跃' : '非活跃' }}
{{ formatDate(user.createTime) }}
暂无数据
创建 api/user.js
文件:
import request from '@/utils/request'
/**
* 获取用户列表(分页)
*/
export function getUserList(params) {
return request({
url: '/api/users',
method: 'get',
params
})
}
/**
* 根据条件搜索用户(分页)
*/
export function searchUsers(data) {
return request({
url: '/api/users/search',
method: 'post',
data
})
}
/**
* 根据年龄范围查询用户
*/
export function getUsersByAgeRange(params) {
return request({
url: '/api/users/age-range',
method: 'get',
params
})
}
/**
* 根据ID获取用户详情
*/
export function getUserById(id) {
return request({
url: `/api/users/${id}`,
method: 'get'
})
}
/**
* 创建用户
*/
export function createUser(data) {
return request({
url: '/api/users',
method: 'post',
data
})
}
/**
* 更新用户
*/
export function updateUser(id, data) {
return request({
url: `/api/users/${id}`,
method: 'put',
data
})
}
/**
* 删除用户
*/
export function deleteUser(id) {
return request({
url: `/api/users/${id}`,
method: 'delete'
})
}
/**
* 批量删除用户
*/
export function batchDeleteUsers(ids) {
return request({
url: '/api/users/batch',
method: 'delete',
data: ids
})
}
创建 utils/request.js
文件:
import axios from 'axios'
// 创建axios实例
const service = axios.create({
baseURL: process.env.VUE_APP_BASE_API || 'http://localhost:8080',
timeout: 10000,
headers: {
'Content-Type': 'application/json'
}
})
// 请求拦截器
service.interceptors.request.use(
config => {
// 在发送请求之前做些什么
// 添加认证token
const token = localStorage.getItem('token')
if (token) {
config.headers['Authorization'] = `Bearer ${token}`
}
// 打印请求信息(开发环境)
if (process.env.NODE_ENV === 'development') {
console.log('发送请求:', config.method.toUpperCase(), config.url, config.params || config.data)
}
return config
},
error => {
console.error('请求错误:', error)
return Promise.reject(error)
}
)
// 响应拦截器
service.interceptors.response.use(
response => {
const res = response.data
// 打印响应信息(开发环境)
if (process.env.NODE_ENV === 'development') {
console.log('收到响应:', response.config.url, res)
}
// 如果响应成功码不是200,则判断为错误
if (res.code !== 200) {
console.error('业务错误:', res.message)
// 特殊错误码处理
if (res.code === 401) {
// 未授权,跳转到登录页
localStorage.removeItem('token')
window.location.href = '/login'
}
return Promise.reject(new Error(res.message || '请求失败'))
}
return res
},
error => {
console.error('响应错误:', error)
let message = '网络错误'
if (error.response) {
// 服务器返回了错误状态码
const status = error.response.status
switch (status) {
case 400:
message = '请求参数错误'
break
case 401:
message = '未授权,请重新登录'
localStorage.removeItem('token')
window.location.href = '/login'
break
case 403:
message = '权限不足'
break
case 404:
message = '请求的资源不存在'
break
case 500:
message = '服务器内部错误'
break
default:
message = `请求失败 (${status})`
}
} else if (error.request) {
// 请求已发出但没有收到响应
message = '网络连接超时'
}
return Promise.reject(new Error(message))
}
)
export default service
对于大量数据的场景,可以使用虚拟滚动来优化性能:
{{ item.username }}
{{ item.email }}
{{ item.age }}
{{ user.username }}
{{ user.email }}
{{ user.age }}岁
加载中...
没有更多数据了
// utils/pageState.js
export class PageStateManager {
constructor(key) {
this.storageKey = `page_state_${key}`
}
// 保存分页状态
saveState(state) {
const pageState = {
pageNum: state.pageNum,
pageSize: state.pageSize,
searchForm: state.searchForm,
timestamp: Date.now()
}
localStorage.setItem(this.storageKey, JSON.stringify(pageState))
}
// 恢复分页状态
restoreState() {
try {
const stored = localStorage.getItem(this.storageKey)
if (!stored) return null
const state = JSON.parse(stored)
// 检查状态是否过期(1小时)
if (Date.now() - state.timestamp > 3600000) {
this.clearState()
return null
}
return state
} catch (error) {
console.error('恢复分页状态失败:', error)
return null
}
}
// 清除分页状态
clearState() {
localStorage.removeItem(this.storageKey)
}
}
// 在Vue组件中使用
export default {
data() {
return {
pageStateManager: new PageStateManager('user_list')
}
},
mounted() {
// 恢复分页状态
const savedState = this.pageStateManager.restoreState()
if (savedState) {
this.pageData.pageNum = savedState.pageNum
this.pageData.pageSize = savedState.pageSize
this.searchForm = { ...this.searchForm, ...savedState.searchForm }
}
this.loadUsers()
},
beforeUnmount() {
// 保存分页状态
this.pageStateManager.saveState({
pageNum: this.pageData.pageNum,
pageSize: this.pageData.pageSize,
searchForm: this.searchForm
})
}
}
-- 为分页查询添加合适的索引
CREATE INDEX idx_users_create_time ON users(create_time);
CREATE INDEX idx_users_status_create_time ON users(status, create_time);
CREATE INDEX idx_users_age_status ON users(age, status);
-- 复合索引优化多条件查询
CREATE INDEX idx_users_search ON users(status, age, create_time);
-- 避免使用SELECT *,明确指定需要的字段
SELECT id, username, email, phone, age, status, create_time
FROM users
WHERE status = 'ACTIVE'
ORDER BY create_time DESC
LIMIT 10 OFFSET 0;
/**
* 用户服务缓存实现
*/
@Service
public class CachedUserService {
@Autowired
private UserMapper userMapper;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* 缓存分页查询结果
*/
public PageResult<User> getUsersWithCache(UserQueryRequest request) {
// 构建缓存key
String cacheKey = buildCacheKey(request);
// 尝试从缓存获取
PageResult<User> cached = (PageResult<User>) redisTemplate.opsForValue().get(cacheKey);
if (cached != null) {
logger.info("从缓存获取分页数据: {}", cacheKey);
return cached;
}
// 从数据库查询
PageResult<User> result = getAllUsers(request);
// 缓存结果(5分钟过期)
redisTemplate.opsForValue().set(cacheKey, result, 5, TimeUnit.MINUTES);
return result;
}
private String buildCacheKey(UserQueryRequest request) {
return String.format("user_page_%d_%d_%s_%s_%s",
request.getPageNum(),
request.getPageSize(),
Objects.toString(request.getUsername(), ""),
Objects.toString(request.getStatus(), ""),
Objects.toString(request.getOrderBy(), ""));
}
}
// 防抖搜索
export default {
data() {
return {
searchDebounceTimer: null
}
},
methods: {
// 防抖搜索
onSearchInputChange() {
clearTimeout(this.searchDebounceTimer)
this.searchDebounceTimer = setTimeout(() => {
this.searchUsers()
}, 300)
},
// 使用requestAnimationFrame优化大量DOM更新
updateUserList(users) {
requestAnimationFrame(() => {
this.userList = users
})
},
// 分批处理大量数据
processBatchUsers(users) {
const batchSize = 100
let index = 0
const processBatch = () => {
const batch = users.slice(index, index + batchSize)
this.processUsers(batch)
index += batchSize
if (index < users.length) {
setTimeout(processBatch, 0)
}
}
processBatch()
}
}
}
后端优化:
前端优化:
用户体验:
安全考虑:
前后端分页功能是Web应用的核心功能之一,合理的分页设计能够显著提升应用性能和用户体验。通过本教程的学习,你应该掌握了:
记住分页不仅仅是技术实现,更要考虑用户体验和系统性能的平衡!