一、PageHelper简介
PageHelper是MyBatis框架的一个强大分页插件,用于简化数据库分页查询操作。它通过拦截SQL执行过程并自动添加分页语句,使开发者可以不必手写复杂的分页SQL,从而大大提高了开发效率。
核心特点
支持多种数据库(MySQL、Oracle、PostgreSQL等)自动识别数据库方言,生成对应的分页语句支持多种分页方式(如静态分页、动态分页)支持连续分页查询(如"上一页"、“下一页”)可自定义分页参数和结果处理
二、基本使用方法
2.1 引入依赖
在Maven项目中添加PageHelper依赖:
2.2 基础配置(Spring Boot)
在application.properties或application.yml中配置PageHelper:
# PageHelper配置
pagehelper:
helper-dialect: mysql # 指定数据库方言
reasonable: true # 开启合理化,页码小于1查询第一页,大于总页数查询最后一页
support-methods-arguments: true # 支持通过Mapper接口参数传递分页参数
page-size-zero: true # pageSize为0时查询所有记录,相当于没有分页
params: count=countSql # 用于从对象中根据属性名取值
2.3 基本使用
方式一:使用startPage静态方法
@RestController
@RequestMapping("/users")
public class UserController {
@Autowired
private UserService userService;
@GetMapping
public PageInfo
@RequestParam(defaultValue = "10") int pageSize) {
// 开始分页,只对之后的第一个查询有效
PageHelper.startPage(pageNum, pageSize);
// 执行查询
List
// 封装分页结果
return new PageInfo<>(users);
}
}
方式二:使用Page对象
@GetMapping("/page")
public PageInfo
// 创建Page对象,构造方法中传入页码和每页记录数
Page
// 使用page对象查询
page = userService.getUserByPage(page);
// 转换为PageInfo对象返回
return new PageInfo<>(page);
}
方式三:分页参数自动获取(针对Spring MVC)
// 在Mapper接口方法中添加Page参数
public interface UserMapper {
List
}
// 在Controller中
@GetMapping("/auto")
public PageInfo
@RequestParam(defaultValue = "10") int pageSize) {
// 直接构造Page对象传给Mapper
Page
List
return new PageInfo<>(users);
}
2.4 PageInfo对象
PageInfo是PageHelper提供的用于封装分页结果的对象,包含完整的分页信息:
PageInfo
// 页码相关信息
int pageNum = pageInfo.getPageNum(); // 当前页码
int pageSize = pageInfo.getPageSize(); // 每页记录数
int pages = pageInfo.getPages(); // 总页数
long total = pageInfo.getTotal(); // 总记录数
// 导航页相关信息
int[] navigatepageNums = pageInfo.getNavigatepageNums(); // 导航页码数组
int navigatePages = pageInfo.getNavigatePages(); // 导航页码数量
// 页面状态
boolean isFirstPage = pageInfo.isIsFirstPage(); // 是否是第一页
boolean isLastPage = pageInfo.isIsLastPage(); // 是否是最后一页
boolean hasPreviousPage = pageInfo.isHasPreviousPage(); // 是否有前一页
boolean hasNextPage = pageInfo.isHasNextPage(); // 是否有下一页
// 数据
List
三、高级用法
3.1 排序查询
// 按id降序排列
PageHelper.startPage(1, 10).orderBy("id desc");
List
// 多字段排序
PageHelper.startPage(1, 10).orderBy("age desc, create_time asc");
List
3.2 count查询优化
// 禁用count查询,适用于不需要总记录数的场景
PageHelper.startPage(1, 10, false);
List
// 自定义count查询SQL
PageHelper.startPage(1, 10).doSelectPageInfo(() -> userService.getAllUsers());
3.3 分页参数设置
// 设置导航页码数量
PageInfo
// 使用默认的每页记录数
Page
3.4 动态获取分页参数
@GetMapping("/dynamic")
public PageInfo
// 从params中动态获取分页参数
int pageNum = params.containsKey("pageNum")
? Integer.parseInt(params.get("pageNum").toString()) : 1;
int pageSize = params.containsKey("pageSize")
? Integer.parseInt(params.get("pageSize").toString()) : 10;
PageHelper.startPage(pageNum, pageSize);
List
return new PageInfo<>(users);
}
3.5 与MyBatis-Plus集成
// 使用MyBatis-Plus的Service接口
@GetMapping("/mybatis-plus")
public IPage
@RequestParam(defaultValue = "10") int pageSize) {
// 创建MP的Page对象
com.baomidou.mybatisplus.extension.plugins.pagination.Page
new com.baomidou.mybatisplus.extension.plugins.pagination.Page<>(pageNum, pageSize);
// 使用MP的分页方法
return userService.page(page);
}
四、注意事项和常见问题
4.1 生命周期问题
注意事项1:PageHelper方法调用后紧跟查询语句
// 错误的用法
PageHelper.startPage(1, 10);
method1(); // 其他方法调用
List
// 正确的用法
PageHelper.startPage(1, 10);
List
注意事项2:一次分页只对一次查询有效
// 错误的用法
PageHelper.startPage(1, 10);
List
List
// 正确的用法
PageHelper.startPage(1, 10);
List
PageHelper.startPage(1, 10); // 需要再次调用
List
4.2 多线程问题
PageHelper使用ThreadLocal存储分页参数,在多线程环境下可能出现问题。
// 多线程环境下的错误用法
PageHelper.startPage(1, 10);
CompletableFuture.runAsync(() -> {
List
});
// 正确的用法
CompletableFuture.runAsync(() -> {
PageHelper.startPage(1, 10); // 在同一线程中调用
List
});
4.3 嵌套查询问题
在嵌套查询或关联查询中,分页可能不符合预期。
// 嵌套查询的问题
PageHelper.startPage(1, 10);
List
// 只有Department会分页,关联的Employee不会分页
// 解决方案:单独查询并手动组装数据
PageHelper.startPage(1, 10);
List
for (Department dept : depts) {
dept.setEmployees(employeeService.getByDeptId(dept.getId()));
}
4.4 count查询性能问题
当数据量大时,count查询可能会影响性能。
// 优化方案1:禁用count查询
PageHelper.startPage(1, 10, false);
List
// 优化方案2:缓存count结果
int totalCount = redisTemplate.opsForValue().get("user:total");
if (totalCount == 0) {
// 执行count查询并缓存结果
PageHelper.startPage(1, 0);
userService.getAllUsers();
totalCount = PageInfo.of(users).getTotal();
redisTemplate.opsForValue().set("user:total", totalCount, 1, TimeUnit.HOURS);
}
4.5 参数合理化问题
默认情况下,PageHelper不会校验页码的合理性,需要配置。
pagehelper:
reasonable: true # 开启合理化,页码小于1查询第一页,大于总页数查询最后一页
五、实战示例
5.1 集成Spring Boot的完整例子
// 实体类
@Data
public class User {
private Long id;
private String username;
private String email;
private Date createTime;
}
// Mapper接口
@Mapper
public interface UserMapper {
List
List
}
// Mapper XML
SELECT * FROM user
SELECT * FROM user
AND username LIKE CONCAT('%', #{username}, '%')
AND email = #{email}
// Service接口
public interface UserService {
PageInfo
PageInfo
}
// Service实现
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Override
public PageInfo
PageHelper.startPage(pageNum, pageSize);
List
return new PageInfo<>(users);
}
@Override
public PageInfo
PageHelper.startPage(pageNum, pageSize);
List
return new PageInfo<>(users);
}
}
// Controller
@RestController
@RequestMapping("/users")
public class UserController {
@Autowired
private UserService userService;
@GetMapping
public Result
@RequestParam(defaultValue = "1") int pageNum,
@RequestParam(defaultValue = "10") int pageSize) {
PageInfo
return Result.success(pageInfo);
}
@GetMapping("/search")
public Result
User user,
@RequestParam(defaultValue = "1") int pageNum,
@RequestParam(defaultValue = "10") int pageSize) {
PageInfo
return Result.success(pageInfo);
}
}
5.2 前端分页展示(Vue + Element UI)
@current-change="handleCurrentChange" @size-change="handleSizeChange" :current-page="pageNum" :page-sizes="[10, 20, 50, 100]" :page-size="pageSize" layout="total, sizes, prev, pager, next, jumper" :total="total">
import axios from 'axios';
export default {
data() {
return {
users: [],
pageNum: 1,
pageSize: 10,
total: 0
};
},
created() {
this.fetchData();
},
methods: {
fetchData() {
axios.get('/api/users', {
params: {
pageNum: this.pageNum,
pageSize: this.pageSize
}
}).then(response => {
const pageInfo = response.data.data;
this.users = pageInfo.list;
this.total = pageInfo.total;
});
},
handleCurrentChange(pageNum) {
this.pageNum = pageNum;
this.fetchData();
},
handleSizeChange(pageSize) {
this.pageSize = pageSize;
this.pageNum = 1; // 页大小变化时重置到第一页
this.fetchData();
}
}
};
六、最佳实践
6.1 合理设置分页大小
根据数据特性和显示需求合理设置每页记录数,避免过大或过小:
过大:影响查询性能和前端加载时间过小:增加页面切换频率,影响用户体验
6.2 使用缓存优化
对于变化不频繁的数据,考虑使用缓存:
@Cacheable(value = "userCache", key = "'page_'+#pageNum+'_'+#pageSize")
public PageInfo
PageHelper.startPage(pageNum, pageSize);
List
return new PageInfo<>(users);
}
6.3 避免大量Join查询
分页查询时避免过多的Join操作,改为先查询主表,再批量查询关联数据:
// 不推荐
PageHelper.startPage(pageNum, pageSize);
List
// 推荐
PageHelper.startPage(pageNum, pageSize);
List
List
Map
users.forEach(user -> user.setRoles(userRoleMap.getOrDefault(user.getId(), Collections.emptyList())));
6.4 合理处理排序
在高并发场景下,使用索引字段进行排序,避免全表排序:
// 推荐使用主键或索引字段排序
PageHelper.startPage(pageNum, pageSize).orderBy("id desc");
List
6.5 封装通用分页方法
创建通用的分页查询方法,减少重复代码:
public class PageUtils {
public static
String orderBy, Supplier> selectSupplier) {
if (pageNum == null || pageSize == null) {
// 不分页
return new PageInfo<>(selectSupplier.get());
}
// 设置分页参数
Page
if (StringUtils.hasText(orderBy)) {
page.setOrderBy(orderBy);
}
// 执行查询
List
return new PageInfo<>(list);
}
}
// 使用方式
PageInfo
() -> userMapper.selectAll());
七、总结
PageHelper是一个强大的MyBatis分页插件,通过简单的配置和调用,可以大大简化分页查询的实现。但在使用过程中需要注意各种细节和潜在问题,如线程安全、生命周期等。通过合理配置和使用最佳实践,PageHelper可以帮助开发者高效实现各种分页查询需求。
希望这篇详细的PageHelper使用指南能够帮助你更好地理解和使用这个工具!