PageHelper是MyBatis框架的一个强大分页插件,用于简化数据库分页查询操作。它通过拦截SQL执行过程并自动添加分页语句,使开发者可以不必手写复杂的分页SQL,从而大大提高了开发效率。
在Maven项目中添加PageHelper依赖:
<dependency>
<groupId>com.github.pagehelpergroupId>
<artifactId>pagehelper-spring-boot-starterartifactId>
<version>1.4.6version>
dependency>
在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 # 用于从对象中根据属性名取值
startPage
静态方法@RestController
@RequestMapping("/users")
public class UserController {
@Autowired
private UserService userService;
@GetMapping
public PageInfo<User> getUsers(@RequestParam(defaultValue = "1") int pageNum,
@RequestParam(defaultValue = "10") int pageSize) {
// 开始分页,只对之后的第一个查询有效
PageHelper.startPage(pageNum, pageSize);
// 执行查询
List<User> users = userService.getAllUsers();
// 封装分页结果
return new PageInfo<>(users);
}
}
Page
对象@GetMapping("/page")
public PageInfo<User> getUsersByPage() {
// 创建Page对象,构造方法中传入页码和每页记录数
Page<User> page = new Page<>(1, 10);
// 使用page对象查询
page = userService.getUserByPage(page);
// 转换为PageInfo对象返回
return new PageInfo<>(page);
}
// 在Mapper接口方法中添加Page参数
public interface UserMapper {
List<User> findAll(Page page);
}
// 在Controller中
@GetMapping("/auto")
public PageInfo<User> getUsersAuto(@RequestParam(defaultValue = "1") int pageNum,
@RequestParam(defaultValue = "10") int pageSize) {
// 直接构造Page对象传给Mapper
Page<User> page = new Page<>(pageNum, pageSize);
List<User> users = userMapper.findAll(page);
return new PageInfo<>(users);
}
PageInfo是PageHelper提供的用于封装分页结果的对象,包含完整的分页信息:
PageInfo<User> pageInfo = new PageInfo<>(users);
// 页码相关信息
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<User> list = pageInfo.getList(); // 当前页数据列表
// 按id降序排列
PageHelper.startPage(1, 10).orderBy("id desc");
List<User> users = userService.getAllUsers();
// 多字段排序
PageHelper.startPage(1, 10).orderBy("age desc, create_time asc");
List<User> users = userService.getAllUsers();
// 禁用count查询,适用于不需要总记录数的场景
PageHelper.startPage(1, 10, false);
List<User> users = userService.getAllUsers();
// 自定义count查询SQL
PageHelper.startPage(1, 10).doSelectPageInfo(() -> userService.getAllUsers());
// 设置导航页码数量
PageInfo<User> pageInfo = new PageInfo<>(users, 5); // 显示5个导航页码
// 使用默认的每页记录数
Page<Object> page = PageHelper.startPage(1); // 使用默认的pageSize
@GetMapping("/dynamic")
public PageInfo<User> getDynamic(@RequestParam Map<String, Object> params) {
// 从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<User> users = userService.getAllUsers();
return new PageInfo<>(users);
}
// 使用MyBatis-Plus的Service接口
@GetMapping("/mybatis-plus")
public IPage<User> getUserWithMP(@RequestParam(defaultValue = "1") int pageNum,
@RequestParam(defaultValue = "10") int pageSize) {
// 创建MP的Page对象
com.baomidou.mybatisplus.extension.plugins.pagination.Page<User> page =
new com.baomidou.mybatisplus.extension.plugins.pagination.Page<>(pageNum, pageSize);
// 使用MP的分页方法
return userService.page(page);
}
注意事项1:PageHelper方法调用后紧跟查询语句
// 错误的用法
PageHelper.startPage(1, 10);
method1(); // 其他方法调用
List<User> users = userService.getAllUsers(); // 不会应用分页
// 正确的用法
PageHelper.startPage(1, 10);
List<User> users = userService.getAllUsers(); // 立即执行分页查询
注意事项2:一次分页只对一次查询有效
// 错误的用法
PageHelper.startPage(1, 10);
List<User> users1 = userService.getAllUsers(); // 应用分页
List<User> users2 = userService.getActiveUsers(); // 不会应用分页
// 正确的用法
PageHelper.startPage(1, 10);
List<User> users1 = userService.getAllUsers(); // 应用分页
PageHelper.startPage(1, 10); // 需要再次调用
List<User> users2 = userService.getActiveUsers(); // 应用分页
PageHelper使用ThreadLocal存储分页参数,在多线程环境下可能出现问题。
// 多线程环境下的错误用法
PageHelper.startPage(1, 10);
CompletableFuture.runAsync(() -> {
List<User> users = userService.getAllUsers(); // 不会应用分页
});
// 正确的用法
CompletableFuture.runAsync(() -> {
PageHelper.startPage(1, 10); // 在同一线程中调用
List<User> users = userService.getAllUsers();
});
在嵌套查询或关联查询中,分页可能不符合预期。
// 嵌套查询的问题
PageHelper.startPage(1, 10);
List<Department> depts = departmentService.getAllWithEmployees();
// 只有Department会分页,关联的Employee不会分页
// 解决方案:单独查询并手动组装数据
PageHelper.startPage(1, 10);
List<Department> depts = departmentService.getAll();
for (Department dept : depts) {
dept.setEmployees(employeeService.getByDeptId(dept.getId()));
}
当数据量大时,count查询可能会影响性能。
// 优化方案1:禁用count查询
PageHelper.startPage(1, 10, false);
List<User> users = userService.getAllUsers();
// 优化方案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);
}
默认情况下,PageHelper不会校验页码的合理性,需要配置。
pagehelper:
reasonable: true # 开启合理化,页码小于1查询第一页,大于总页数查询最后一页
// 实体类
@Data
public class User {
private Long id;
private String username;
private String email;
private Date createTime;
}
// Mapper接口
@Mapper
public interface UserMapper {
List<User> selectAll();
List<User> selectByCondition(User user);
}
// Mapper XML
<mapper namespace="com.example.mapper.UserMapper">
<select id="selectAll" resultType="User">
SELECT * FROM user
</select>
<select id="selectByCondition" resultType="User" parameterType="User">
SELECT * FROM user
<where>
<if test="username != null and username != ''">
AND username LIKE CONCAT('%', #{username}, '%')
</if>
<if test="email != null and email != ''">
AND email = #{email}
</if>
</where>
</select>
</mapper>
// Service接口
public interface UserService {
PageInfo<User> getUsers(int pageNum, int pageSize);
PageInfo<User> getUsersByCondition(User user, int pageNum, int pageSize);
}
// Service实现
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Override
public PageInfo<User> getUsers(int pageNum, int pageSize) {
PageHelper.startPage(pageNum, pageSize);
List<User> users = userMapper.selectAll();
return new PageInfo<>(users);
}
@Override
public PageInfo<User> getUsersByCondition(User user, int pageNum, int pageSize) {
PageHelper.startPage(pageNum, pageSize);
List<User> users = userMapper.selectByCondition(user);
return new PageInfo<>(users);
}
}
// Controller
@RestController
@RequestMapping("/users")
public class UserController {
@Autowired
private UserService userService;
@GetMapping
public Result<PageInfo<User>> listUsers(
@RequestParam(defaultValue = "1") int pageNum,
@RequestParam(defaultValue = "10") int pageSize) {
PageInfo<User> pageInfo = userService.getUsers(pageNum, pageSize);
return Result.success(pageInfo);
}
@GetMapping("/search")
public Result<PageInfo<User>> searchUsers(
User user,
@RequestParam(defaultValue = "1") int pageNum,
@RequestParam(defaultValue = "10") int pageSize) {
PageInfo<User> pageInfo = userService.getUsersByCondition(user, pageNum, pageSize);
return Result.success(pageInfo);
}
}
根据数据特性和显示需求合理设置每页记录数,避免过大或过小:
对于变化不频繁的数据,考虑使用缓存:
@Cacheable(value = "userCache", key = "'page_'+#pageNum+'_'+#pageSize")
public PageInfo<User> getUsers(int pageNum, int pageSize) {
PageHelper.startPage(pageNum, pageSize);
List<User> users = userMapper.selectAll();
return new PageInfo<>(users);
}
分页查询时避免过多的Join操作,改为先查询主表,再批量查询关联数据:
// 不推荐
PageHelper.startPage(pageNum, pageSize);
List<User> users = userMapper.selectWithRolesAndPermissions();
// 推荐
PageHelper.startPage(pageNum, pageSize);
List<User> users = userMapper.selectAll();
List<Long> userIds = users.stream().map(User::getId).collect(Collectors.toList());
Map<Long, List<Role>> userRoleMap = roleService.getUserRoles(userIds);
users.forEach(user -> user.setRoles(userRoleMap.getOrDefault(user.getId(), Collections.emptyList())));
在高并发场景下,使用索引字段进行排序,避免全表排序:
// 推荐使用主键或索引字段排序
PageHelper.startPage(pageNum, pageSize).orderBy("id desc");
List<User> users = userMapper.selectAll();
创建通用的分页查询方法,减少重复代码:
public class PageUtils {
public static <T> PageInfo<T> startPage(Integer pageNum, Integer pageSize,
String orderBy, Supplier<List<T>> selectSupplier) {
if (pageNum == null || pageSize == null) {
// 不分页
return new PageInfo<>(selectSupplier.get());
}
// 设置分页参数
Page<T> page = PageHelper.startPage(pageNum, pageSize);
if (StringUtils.hasText(orderBy)) {
page.setOrderBy(orderBy);
}
// 执行查询
List<T> list = selectSupplier.get();
return new PageInfo<>(list);
}
}
// 使用方式
PageInfo<User> pageInfo = PageUtils.startPage(1, 10, "create_time desc",
() -> userMapper.selectAll());
PageHelper是一个强大的MyBatis分页插件,通过简单的配置和调用,可以大大简化分页查询的实现。但在使用过程中需要注意各种细节和潜在问题,如线程安全、生命周期等。通过合理配置和使用最佳实践,PageHelper可以帮助开发者高效实现各种分页查询需求。
希望这篇详细的PageHelper使用指南能够帮助你更好地理解和使用这个工具!