大多数系统都需要分析功能。通常都是用物理分页实现,比如我们用关系数据库时是使用 SQL 语句提供的分页参数实现(比如 MySQL 的 limit 参数)。但在不同的关系数据库中,SQL 语句的分页参数都不同,所以有一些框架会提供通用的分页功能,屏蔽不同数据库的分页方式。在使用 MyBatis 操作数据库的 WEB 系统中,我们可以使用 PageHelper 插件轻松实现支持各种数据库的分页功能。关于该插件的介绍和使用请前往 GitHub地址:github.com/pagehelper/… 。
目前经手的项目中,分页功能都是使用了 PageHelper 插件。调试 PageHelper 源码可以知道它是基于 MyBatis 的拦截器功能扩展实现的。不过这篇文章想说的是项目中遇到的一个使用 PageHelper 的小问题,这里记录下。
问题代码
@GetMapping("/users")
public PageInfo<UserDTO> listUsers(@RequestParam int pageNum,
@RequestParam int pageSize,
@RequestParam String user,
@RequestParam String phone) {
// 开启分页
PageHelper.startPage(pageNum, pageSize);
// 多条件查询用户信息列表
List<User> users = userService.list(user, phone);
// 处理users,返回需要的信息
List<UserDTO> userDTOs = convertUser(users);
// 包装users,返回分页和数据信息
return new PageInfo(userDTOs);}
/**
* 返回users中需要的信息,其他字段不返回
*/
public List<UserDTO> convertUser(List<User> users) {
List<UserDTO> userDTOs = new ArrayList<>();
if(users != null && !users.isEmpty()) {
users.foreach(user -> {
UserDTO userDTO = new UserDTO();
userDTO.setUserName(user.getUserName);
userDTO.setPhone(user.getPhone);
userDTOs.add(userDTO);
});
}
return userDTOs;
}
注意:想要开启分页,必须使用 PageHelper.startPage(pageNum, pageSize); 而且使用该语句后,仅仅对其后的第一条查询语句产生分页效果。
查询结果
利用 PostMan 查询该接口,返回信息如下截图:
pageNum=1&pageSize=1 时的查询结果
pageNum=1&pageSize=10 时的查询结果
上面两次查询,图 1 的结果中分页参数是有问题的。比如总条数 count 和当前条数 currentCount 相同,总页数 pages 是 1。这明显是不对的。正确的结果应该是:图 1 的 count = 3, currentCount = 1, pages = 2;
分析users的真实类型
问题代码中的查询语句:List<User> users = userService.list(userName, phone); 返回值 users 其实并不是 ArrayList 的实例,因为开启了分页,所以 users是 PageHelper 插件中的 Page 类的实例。所以返回值可以用Page接受(注意:不能用PageInfo接收)
Page<User> users = userService.list(user, phone); 返回值可以用Page<User>接收
PageHelper 插件的原理是通过 MyBatis 的拦截器机制实现的,以下是 PageHelper 的拦截器的分页查询逻辑:
runtimeDialect.afterPage(resultList, parameterObject, rowBounds); 代码如下:
图中执行分页查询 executor.query(......) 后的结果 resultList 会被放到 page 对象中,即 page.addAll(pageList); PageHelper 中的类 Page 是继承 ArrayList 类。最后这个 page 会被赋值给代码中的 users 引用(通过 Object 做强制类型转换)。
错误原因分析
上面分析了 users 的真实类型是 Page 类的实例。所以当调用了 convertUsers(users) 方法后会丢失分页相关的参数信息,返回值 userDTOs 已经不是 Page 的实例了,userDTOs 只是普通的 ArrayList 实例,没有分页相关的参数信息,比如 count, pages 等。执行 new PageInfo(userDTOs) 时的源码如下:
即 userDTOs 是 Collection 子类实例,所以执行了红框中的逻辑,也就导致了我们上面两次查询中错误结果。
解决办法
新建一个 PageInfo 对象,将分页参数和需要重新构建的数据设置到新的 PageInfo 中即可。代码如下:
@GetMapping("/users")
public PageInfo<UserDTO> listUsers(@RequestParam int pageNum,
@RequestParam int pageSize,
@RequestParam String user,
@RequestParam String phone) {
// 开启分页
PageHelper.startPage(pageNum, pageSize);
// 多条件查询用户信息列表
List<User> users = userService.list(user, phone);
PageInfo<UserDTO> userDTOPageInfo = PageInfoUtil.pageInfo2PageInfo(new PageInfo(users)); // 处理users,设置给userDTOPageInfo
convertUser(users, userDTOPageInfo);
// 包装users,返回分页和数据信息
return userDTOPageInfo;}
/**
* 返回users中需要的信息,其他字段不返回
*/
public List<UserDTO> convertUser(List<User> users, PageInfo<UserDTO> userDTOPageInfo) {
List<UserDTO> userDTOs = new ArrayList<>();
if(users != null && !users.isEmpty()) {
users.foreach(user -> {
UserDTO userDTO = new UserDTO();
userDTO.setUserName(user.getUserName);
userDTO.setPhone(user.getPhone);
// 将新建的数据放入 userDTOPageInfo 中
userDTOPageInfo.getList().add(userDTO); });
}
return userDTOPageInfo;}
public class PageInfoUtil {
/**
* pageInfo对象转换的泛型方法
* P :入参类型 V:出参类型
*/
public static <P, V> PageInfo<V> PageInfo2PageInfoVo(PageInfo<P> pageInfoPo) {
// 创建Page对象,实际上是一个ArrayList类型的集合
Page<V> page = new Page<>(pageInfoPo.getPageNum(), pageInfoPo.getPageSize());
// 设置总记录数
page.setTotal(pageInfoPo.getTotal());
// 创建 PageInfo 对象
PageInfo pageInfo = new PageInfo<>(page);
// 设置当前记录总数
pageInfo.setSize(pageInfoPo.getList().size());
return pageInfo;
}
}
历史文章