一、PageHelper使用背景
公司要做个简单管理系统,要我搭建Spring Boot+MyBatis+PageHelper+Redis的项目框架然后交i给实习生来开发。这个其实很简单,但是遇到搭建和使用过程中PageHelper有好多小坑,就记录一下,避免再踩。
版本选择:
JDK 8
SpringBoot 2.5.0
MyBatis 3.5.7
PageHelper 5.2.0
二、步骤
2.1 新建Spring Boot项目
如果过程中,选择java版本时发现没有java8版本,只有java17和java21
原因:
spring2.X版本在2023年11月24日停止维护,因此创建spring项目时不再有2.X版本的选项,只能从3.1.X版本开始选择
而Spring3.X版本不支持JDK8,JDK11,最低支持JDK17,因此JDK11也无法选择
解决:
目前阿里云支持创建Spring2.X版本的项目
修改Server URL为:https://start.aliyun.com
这样就可以创建啦
2.2 引入依赖
在pom.xml文件中添加相关依赖:
<dependencies>
<!-- Spring Boot Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- MyBatis Starter -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.0</version>
</dependency>
<!-- PageHelper -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.4.0</version>
</dependency>
</dependencies>
2.3 配置PageHelper
在application.yml文件中进行PageHelper的基本配置:
pagehelper:
helper-dialect: mysql # 指定数据库方言为MySQL
reasonable: true # 分页合理化,启用后如果页码<1则查询第一页,页码>总页数则查询最后一页。
support-methods-arguments: true # 支持通过Mapper接口参数来传递分页参数
params: count=countSql # 指定count查询的参数名称
2.4 配置MyBatis
让PageHelper与MyBatis集成,还需在SpringBoot配置文件中添加MyBatis的相关配置:
mybatis:
mapper-locations: classpath:/mappers/*.xml # Mapper XML文件的位置
type-aliases-package: com.example.demo.entity # 实体类的包路径
2.5 编写Mapper接口和XML
User实体类:
public class User {
private Long id;
private String name;
private String email;
// getters and setters
}
对应的Mapper接口:
public interface UserMapper {
@Select("SELECT * FROM users")
List<User> selectAll();
}
Mapper XML文件则包含分页查询的SQL:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.UserMapper">
<select id="selectAll" resultType="com.example.demo.entity.User">
SELECT * FROM users
</select>
</mapper>
2.6 使用PageHelper进行分页
Service层使用PageHelper进行分页查询:
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
public PageInfo<User> getUsers(int pageNum, int pageSize) {
PageHelper.startPage(pageNum, pageSize);
List<User> users = userMapper.selectAll();
return new PageInfo<>(users);
}
}
在Controller层,通过RESTful接口来调用分页查询:
@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) {
return userService.getUsers(pageNum, pageSize);
}
}
这就实现简单的分页查询功能,通过PageHelper来控制分页参数。
三、问题解决
(1)分页无效或查询结果为空
确保在调用分页查询方法前,已经正确调用了PageHelper.startPage方法。
检查数据库连接是否正常,SQL查询语句是否正确。
(2)分页参数不生效
检查Controller层是否正确接收并传递分页参数。
确保application.yml中配置的support-methods-arguments为true。
(3)性能问题
对于大数据量的表,分页查询可能会带来性能问题。可以通过增加索引、优化SQL查询等方式提高性能。
(4)使用过程中线程污染,无缘故的分页
前端调用一个未分页的接口,出现数据丢失或者报错的情况:
现象:前端调用一个只查询一条数据的接口,该接口执行的SQL是:
select id,statistics_month,update_time from business_statistics_record
order by statistics_month desclimit 1
但是实际上,日记打印出来的SQL:limit 1 limit ?, ?;就出现查询异常:
经过排查,真正原因是因为调用自定义分页出现问题:PageHelper.startPage(pageNum, pageSize);调用之后并没有消费,分页参数一直保存在线程中,当这个线程再次调用的时候,导致莫名奇妙的加上limit关键字。
查看PageHelper源码看到:
PageHelper 方法使用静态的 ThreadLocal参数,分页参数和线程是绑定的。只要保证在 PageHelper方法调用后紧跟MyBatis查询方法,这就是安全的。因为 PageHelper在finally代码段中自动清除ThreadLocal存储的对象。
而随机加上limit关键字,查看ThreadLocal LOCAL_PAGE值的变化,只有当线程复用的时候才会出现LOCAL_PAGE已被实例化。
为避免使用PageHelper过程中如果出现无缘无故出现分页,
在使用了PageHelper.startPage()后需要紧接着 MyBatis 查询方法。
最好是在执行sql的方法加上finally语句清理page缓存:
这个afterAll()方法中:
而 clearPage()方法的功能是: