MyBatis 分页插件:PageHelper 的集成与使用 – 让你的数据飞起来!
各位观众,晚上好!欢迎来到“告别手写分页,拥抱优雅人生”特别节目。今天,我们不聊鸡汤,只聊干货,主题就是:MyBatis 分页插件 PageHelper 的集成与使用。
各位是不是经常遇到这样的场景:页面上显示一个商品列表,噼里啪啦一大堆数据,恨不得把服务器撑爆。然后,老板大手一挥:“加个分页!” 你心里默默OS:“又要写重复的代码了…”。 别怕,今天我们就来解决这个“重复性造轮子”的问题,让你的数据飞起来!
为什么要用分页插件?
在没有分页插件的时代,我们是怎么做的呢?
- 手动计算分页参数: 你要自己算
limit
和offset
,算不好还容易出错。 - 编写重复的 SQL: 每次分页都要改 SQL,写多了眼睛都花了。
- 代码冗余: Dao 层、Service 层到处都是分页相关的逻辑,简直乱成一锅粥。
简单来说,就是费时费力,效率低下。而分页插件就像一位贴心的管家,帮你把这些繁琐的事情都处理了,你只需要专注于业务逻辑即可。
PageHelper 简介
PageHelper 是一个 MyBatis 的分页插件,它通过 MyBatis 的拦截器机制,在 SQL 执行之前修改 SQL 语句,自动添加分页参数,从而实现分页功能。 它的优点:
- 使用简单: 只需要简单的配置,即可实现分页。
- 零侵入: 不需要修改原有的代码,即可实现分页。
- 功能强大: 支持多种数据库,支持多种分页方式。
- 性能高效: 对性能影响很小。
总之,用了 PageHelper,腰不酸了,腿不疼了,写代码也更有劲了!
集成 PageHelper
接下来,我们手把手教你如何集成 PageHelper。
1. 添加 Maven 依赖:
首先,在你的 pom.xml
文件中添加 PageHelper 的 Maven 依赖。你需要根据你的 MyBatis 版本选择对应的 PageHelper 版本。 这里我们以常用的 MyBatis 3 和 PageHelper 5 为例:
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.4.6</version> <!-- 建议使用最新版本 -->
</dependency>
注意:pagehelper-spring-boot-starter
适用于 Spring Boot 项目,如果是 Spring 项目,需要使用 pagehelper
依赖。
2. 配置 PageHelper:
在 Spring Boot 项目中,PageHelper 会自动配置,你只需要在 application.properties
或 application.yml
文件中进行一些必要的配置即可。
# application.properties
pagehelper.helper-dialect=mysql # 指定数据库方言
pagehelper.reasonable=true # 分页参数合理化,防止页码越界
pagehelper.support-methods-arguments=true # 支持通过 Mapper 接口参数来传递分页参数
pagehelper.params=count=countSql # 为了支持 count 查询,设置该参数
或者,在 application.yml
中:
pagehelper:
helper-dialect: mysql
reasonable: true
support-methods-arguments: true
params: count=countSql
参数说明:
参数名 | 含义 |
---|---|
helper-dialect |
指定数据库方言,例如:mysql 、oracle 、postgresql 等。PageHelper 会根据不同的数据库方言,生成不同的分页 SQL。 |
reasonable |
分页参数合理化。当 pageNum <= 0 时,会查询第一页;当 pageNum > 总页数时,会查询最后一页。 默认值为 false 。 |
support-methods-arguments |
支持通过 Mapper 接口参数来传递分页参数。例如,可以在 Mapper 接口的方法中直接传递 pageNum 和 pageSize 参数。 默认值为 false 。 |
params |
为了支持 count 查询,需要设置该参数。count=countSql 表示使用名为 countSql 的参数来指定 count 查询的 SQL。 |
auto-runtime-dialect |
自动获取数据库方言。 默认值为 false 。 如果设置为 true ,PageHelper 会自动根据数据库连接信息获取数据库方言,无需手动指定 helper-dialect 。 但建议还是手动指定,避免出现意外情况。 |
close-conn |
是否关闭数据库连接。 默认值为 true 。 如果设置为 false ,PageHelper 不会关闭数据库连接,需要手动关闭。 |
3. 使用 PageHelper:
配置完成后,就可以在代码中使用 PageHelper 了。使用方式非常简单,只需要在查询之前调用 PageHelper.startPage()
方法即可。
示例:
假设我们有一个 UserMapper
接口,用于查询用户信息。
public interface UserMapper {
List<User> selectAllUsers();
List<User> selectUsersByName(String name);
}
在 Service 层,我们可以这样使用 PageHelper:
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
public PageInfo<User> getAllUsers(int pageNum, int pageSize) {
// 开启分页
PageHelper.startPage(pageNum, pageSize);
// 执行查询
List<User> users = userMapper.selectAllUsers();
// 封装分页信息
return new PageInfo<>(users);
}
public PageInfo<User> getUsersByName(String name, int pageNum, int pageSize) {
PageHelper.startPage(pageNum, pageSize);
List<User> users = userMapper.selectUsersByName(name);
return new PageInfo<>(users);
}
}
代码解释:
PageHelper.startPage(pageNum, pageSize)
:该方法用于开启分页。pageNum
表示当前页码,pageSize
表示每页显示的数量。List<User> users = userMapper.selectAllUsers()
:执行查询操作。 注意,在startPage()
方法之后执行的第一个查询操作,会被 PageHelper 拦截并添加分页参数。new PageInfo<>(users)
:将查询结果封装成PageInfo
对象。PageInfo
对象包含了分页相关的信息,例如:总记录数、总页数、当前页码、每页显示的数量等。
4. Mapper.xml 文件:
你的Mapper.xml 文件无需任何改动,保持原来的样子即可。 例如:
<mapper namespace="com.example.mapper.UserMapper">
<select id="selectAllUsers" resultType="com.example.entity.User">
SELECT * FROM user
</select>
<select id="selectUsersByName" parameterType="java.lang.String" resultType="com.example.entity.User">
SELECT * FROM user WHERE name LIKE CONCAT('%', #{name}, '%')
</select>
</mapper>
5. Controller 层:
在Controller层,我们接收前端传递的 pageNum
和 pageSize
参数,并调用 Service 层的方法进行查询。
@RestController
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/users")
public PageInfo<User> getUsers(@RequestParam(defaultValue = "1") int pageNum,
@RequestParam(defaultValue = "10") int pageSize) {
return userService.getAllUsers(pageNum, pageSize);
}
@GetMapping("/users/search")
public PageInfo<User> searchUsers(@RequestParam String name,
@RequestParam(defaultValue = "1") int pageNum,
@RequestParam(defaultValue = "10") int pageSize) {
return userService.getUsersByName(name, pageNum, pageSize);
}
}
6. 前端展示:
前端接收到 PageInfo
对象后,可以根据其中的数据进行展示。 PageInfo
对象包含以下常用的属性:
属性名 | 含义 |
---|---|
list |
当前页的数据列表 |
total |
总记录数 |
pageNum |
当前页码 |
pageSize |
每页显示的数量 |
pages |
总页数 |
isFirstPage |
是否是第一页 |
isLastPage |
是否是最后一页 |
hasPreviousPage |
是否有上一页 |
hasNextPage |
是否有下一页 |
PageHelper 高级用法
除了基本的分页功能外,PageHelper 还提供了一些高级用法,可以满足更复杂的需求。
1. 使用 RowBounds 分页:
RowBounds 是 MyBatis 提供的一种分页方式,PageHelper 也支持使用 RowBounds 进行分页。 这种方式不需要修改 SQL 语句,但是需要手动计算分页参数。
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
public PageInfo<User> getUsersByRowBounds(int pageNum, int pageSize) {
RowBounds rowBounds = new RowBounds((pageNum - 1) * pageSize, pageSize);
List<User> users = userMapper.selectAllUsers(rowBounds);
return new PageInfo<>(users);
}
}
对应的 Mapper 接口:
public interface UserMapper {
List<User> selectAllUsers(RowBounds rowBounds);
}
对应的 Mapper.xml 文件:
<mapper namespace="com.example.mapper.UserMapper">
<select id="selectAllUsers" resultType="com.example.entity.User">
SELECT * FROM user
</select>
</mapper>
注意: 使用 RowBounds 时,PageInfo
对象中的 total
属性为 0,因为 PageHelper 无法获取总记录数。
2. 使用 Count 查询:
默认情况下,PageHelper 会自动执行 count 查询,获取总记录数。 但是,如果你的 count 查询比较复杂,或者你想自定义 count 查询,可以使用 countSql
参数。
首先,在 Mapper.xml 文件中定义 count 查询的 SQL:
<mapper namespace="com.example.mapper.UserMapper">
<select id="selectAllUsers" resultType="com.example.entity.User">
SELECT * FROM user
</select>
<select id="countAllUsers" resultType="java.lang.Long">
SELECT COUNT(*) FROM user
</select>
</mapper>
然后,在 Service 层中使用 PageHelper.startPage()
方法时,指定 countSql
参数:
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
public PageInfo<User> getAllUsersWithCustomCount(int pageNum, int pageSize) {
PageHelper.startPage(pageNum, pageSize, "countAllUsers"); // 指定 countSql
List<User> users = userMapper.selectAllUsers();
return new PageInfo<>(users);
}
}
3. 多个 PageHelper.startPage() 嵌套:
在某些特殊情况下,你可能需要在同一个方法中执行多个分页查询。 PageHelper 支持多个 startPage()
方法嵌套使用。 但是,你需要注意以下几点:
- 每次调用
startPage()
方法之前,都要确保之前的startPage()
方法已经执行完毕。 - 每次调用
startPage()
方法之后,都要立即执行对应的查询操作。
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
public void multiplePageHelper() {
// 第一个分页查询
PageHelper.startPage(1, 10);
List<User> users = userMapper.selectAllUsers();
PageInfo<User> userPageInfo = new PageInfo<>(users);
// 第二个分页查询
PageHelper.startPage(1, 5);
List<User> anotherUsers = userMapper.selectAllUsers();
PageInfo<User> anotherUserPageInfo = new PageInfo<>(anotherUsers);
// ...
}
}
4. 使用PageInterceptor 手动分页:
如果你想更加灵活地控制分页过程,可以使用 PageInterceptor
类。
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
@Autowired
private SqlSessionFactory sqlSessionFactory;
public PageInfo<User> getAllUsersManual(int pageNum, int pageSize) throws Throwable {
// 创建 PageInterceptor 对象
PageInterceptor interceptor = new PageInterceptor();
// 设置分页参数
Properties properties = new Properties();
properties.setProperty("helperDialect", "mysql");
properties.setProperty("reasonable", "true");
interceptor.setProperties(properties);
// 获取 Configuration 对象
Configuration configuration = sqlSessionFactory.getConfiguration();
// 创建 BoundSql 对象
MappedStatement mappedStatement = configuration.getMappedStatement("com.example.mapper.UserMapper.selectAllUsers");
BoundSql boundSql = mappedStatement.getBoundSql(null);
// 创建 Invocation 对象
Invocation invocation = new Invocation(mappedStatement.getSqlSource(), new Object[]{null, null}, new Class[]{RowBounds.class, ResultHandler.class});
// 使用 PageInterceptor 进行分页
interceptor.intercept(invocation);
// 获取分页后的 SQL
BoundSql newBoundSql = mappedStatement.getBoundSql(null);
String sql = newBoundSql.getSql();
// 执行查询
List<User> users = sqlSessionFactory.openSession().selectList("com.example.mapper.UserMapper.selectAllUsers", null, new RowBounds((pageNum - 1) * pageSize, pageSize));
// 获取总记录数
long total = (long) sqlSessionFactory.openSession().selectOne("com.example.mapper.UserMapper.countAllUsers");
// 封装分页信息
PageInfo<User> pageInfo = new PageInfo<>(users);
pageInfo.setTotal(total);
pageInfo.setPageNum(pageNum);
pageInfo.setPageSize(pageSize);
pageInfo.setPages((int) Math.ceil((double) total / pageSize));
return pageInfo;
}
}
注意: 这种方式比较复杂,需要手动处理很多细节。 建议在对 PageHelper 的原理有深入了解后再使用。
常见问题及解决方案
在使用 PageHelper 的过程中,可能会遇到一些问题。 下面列举一些常见问题及解决方案:
1. 分页无效:
- 原因:
PageHelper.startPage()
方法没有在查询之前调用。 - 解决方案: 确保
PageHelper.startPage()
方法在查询之前被调用。
2. Count 查询出错:
- 原因:
helper-dialect
配置错误,或者数据库方言不支持。 - 解决方案: 检查
helper-dialect
配置是否正确,或者尝试使用其他数据库方言。
3. 总记录数为 0:
- 原因: 使用了 RowBounds 分页,或者 count 查询的 SQL 语句有问题。
- 解决方案: 如果使用了 RowBounds 分页,需要手动设置
PageInfo
对象的total
属性。 检查 count 查询的 SQL 语句是否正确。
4. 数据库连接未关闭:
- 原因:
close-conn
配置为false
。 - 解决方案: 将
close-conn
配置为true
,或者手动关闭数据库连接。
5. 多数据源问题:
如果你的项目使用了多数据源,需要为每个数据源配置一个 PageHelper 插件。 你可以使用 @Configuration
和 @Bean
注解来实现:
@Configuration
public class PageHelperConfig {
@Bean
public PageInterceptor pageInterceptor1() {
PageInterceptor interceptor = new PageInterceptor();
Properties properties = new Properties();
properties.setProperty("helperDialect", "mysql");
properties.setProperty("reasonable", "true");
interceptor.setProperties(properties);
return interceptor;
}
@Bean
public PageInterceptor pageInterceptor2() {
PageInterceptor interceptor = new PageInterceptor();
Properties properties = new Properties();
properties.setProperty("helperDialect", "oracle");
properties.setProperty("reasonable", "true");
interceptor.setProperties(properties);
return interceptor;
}
}
然后在 MyBatis 的配置中,将这些 PageInterceptor 添加进去。 具体配置方式可以参考 MyBatis 的官方文档。
总结
PageHelper 是一个非常实用的 MyBatis 分页插件,它可以帮助我们快速、简单地实现分页功能,提高开发效率。 通过本文的介绍,相信你已经掌握了 PageHelper 的集成和使用方法。 赶快用起来,告别手写分页的烦恼吧!
希望今天的节目对大家有所帮助。 感谢大家的收看,我们下期再见! 记住,代码要写得优雅,生活也要过得精彩!