PageHelper详解:使用方法与注意事项

一、PageHelper简介

PageHelper是MyBatis框架的一个强大分页插件,用于简化数据库分页查询操作。它通过拦截SQL执行过程并自动添加分页语句,使开发者可以不必手写复杂的分页SQL,从而大大提高了开发效率。

核心特点

  • 支持多种数据库(MySQL、Oracle、PostgreSQL等)
  • 自动识别数据库方言,生成对应的分页语句
  • 支持多种分页方式(如静态分页、动态分页)
  • 支持连续分页查询(如"上一页"、“下一页”)
  • 可自定义分页参数和结果处理

二、基本使用方法

2.1 引入依赖

在Maven项目中添加PageHelper依赖:


<dependency>
    <groupId>com.github.pagehelpergroupId>
    <artifactId>pagehelper-spring-boot-starterartifactId>
    <version>1.4.6version>  
dependency>

2.2 基础配置(Spring Boot)

application.propertiesapplication.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<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);
}
方式三:分页参数自动获取(针对Spring MVC)
// 在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);
}

2.4 PageInfo对象

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();  // 当前页数据列表

三、高级用法

3.1 排序查询

// 按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();

3.2 count查询优化

// 禁用count查询,适用于不需要总记录数的场景
PageHelper.startPage(1, 10, false);
List<User> users = userService.getAllUsers();

// 自定义count查询SQL
PageHelper.startPage(1, 10).doSelectPageInfo(() -> userService.getAllUsers());

3.3 分页参数设置

// 设置导航页码数量
PageInfo<User> pageInfo = new PageInfo<>(users, 5);  // 显示5个导航页码

// 使用默认的每页记录数
Page<Object> page = PageHelper.startPage(1);  // 使用默认的pageSize

3.4 动态获取分页参数

@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);
}

3.5 与MyBatis-Plus集成

// 使用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);
}

四、注意事项和常见问题

4.1 生命周期问题

注意事项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();  // 应用分页

4.2 多线程问题

PageHelper使用ThreadLocal存储分页参数,在多线程环境下可能出现问题。

// 多线程环境下的错误用法
PageHelper.startPage(1, 10);
CompletableFuture.runAsync(() -> {
    List<User> users = userService.getAllUsers();  // 不会应用分页
});

// 正确的用法
CompletableFuture.runAsync(() -> {
    PageHelper.startPage(1, 10);  // 在同一线程中调用
    List<User> users = userService.getAllUsers();
});

4.3 嵌套查询问题

在嵌套查询或关联查询中,分页可能不符合预期。

// 嵌套查询的问题
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()));
}

4.4 count查询性能问题

当数据量大时,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);
}

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<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);
    }
}

5.2 前端分页展示(Vue + Element UI)




六、最佳实践

6.1 合理设置分页大小

根据数据特性和显示需求合理设置每页记录数,避免过大或过小:

  • 过大:影响查询性能和前端加载时间
  • 过小:增加页面切换频率,影响用户体验

6.2 使用缓存优化

对于变化不频繁的数据,考虑使用缓存:

@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);
}

6.3 避免大量Join查询

分页查询时避免过多的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())));

6.4 合理处理排序

在高并发场景下,使用索引字段进行排序,避免全表排序:

// 推荐使用主键或索引字段排序
PageHelper.startPage(pageNum, pageSize).orderBy("id desc");
List<User> users = userMapper.selectAll();

6.5 封装通用分页方法

创建通用的分页查询方法,减少重复代码:

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使用指南能够帮助你更好地理解和使用这个工具!

你可能感兴趣的:(spring,boot,java)