MyBatis 分页插件:PageHelper 的集成与使用

MyBatis 分页插件:PageHelper 的集成与使用 – 让你的数据飞起来!

各位观众,晚上好!欢迎来到“告别手写分页,拥抱优雅人生”特别节目。今天,我们不聊鸡汤,只聊干货,主题就是:MyBatis 分页插件 PageHelper 的集成与使用。

各位是不是经常遇到这样的场景:页面上显示一个商品列表,噼里啪啦一大堆数据,恨不得把服务器撑爆。然后,老板大手一挥:“加个分页!” 你心里默默OS:“又要写重复的代码了…”。 别怕,今天我们就来解决这个“重复性造轮子”的问题,让你的数据飞起来!

为什么要用分页插件?

在没有分页插件的时代,我们是怎么做的呢?

  1. 手动计算分页参数: 你要自己算 limitoffset,算不好还容易出错。
  2. 编写重复的 SQL: 每次分页都要改 SQL,写多了眼睛都花了。
  3. 代码冗余: 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.propertiesapplication.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 指定数据库方言,例如:mysqloraclepostgresql 等。PageHelper 会根据不同的数据库方言,生成不同的分页 SQL。
reasonable 分页参数合理化。当 pageNum <= 0 时,会查询第一页;当 pageNum > 总页数时,会查询最后一页。 默认值为 false
support-methods-arguments 支持通过 Mapper 接口参数来传递分页参数。例如,可以在 Mapper 接口的方法中直接传递 pageNumpageSize 参数。 默认值为 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层,我们接收前端传递的 pageNumpageSize 参数,并调用 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 的集成和使用方法。 赶快用起来,告别手写分页的烦恼吧!

希望今天的节目对大家有所帮助。 感谢大家的收看,我们下期再见! 记住,代码要写得优雅,生活也要过得精彩!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注