MyBatis是如何分页的及原理

MyBatis 是一种持久层框架,支持通过配置文件和注解将 SQL 映射为 Java 对象。在实际开发中,查询数据时经常需要进行分页处理。 MyBatis 也提供了支持分页的方案,其主要思路是使用 Limit 偏移量和限制个数,来获取指定数量的数据。下面将会介绍 MyBatis 如何进行分页。

MyBatis 提供两种分页方式:基于参数改造和基于插件拦截 。下面我们将分别介绍这两种方式:

1、基于参数改造:

第一种分页方式是基于参数改造的,通过添加参数 limit 和 offset 就可以实现查询从某个位置开始的若干条记录,代码实现如下:

复制代码
<select id="selectSomeData"
    parameterType="map" resultType="com.example.SomeData">
        SELECT * FROM sometable
        ORDER BY somecolumn
        LIMIT #{limit} OFFSET #{offset}
</select>

这段 SQL 语句会返回从偏移量为 offset 的位置开始的 limit 条结果。例如:LIMIT 30,10 表示从第 31 行开始返回 10 行结果。

在实际应用中,我们可以将 limit 和 offset 抽取成两个参数,并传入到 MyBatis 中。

2、基于插件拦截 :

MyBatis 还提供了另外一种分页方式,基于插件拦截机制。这种方式更加灵活,支持实现更为复杂的分页功能。

我们需要自定义一个拦截器,实现 Interceptor 接口,并重写其中唯一的 intercept 方法,在其中对 SQL 语句进行修改,添加分页信息。具体操作如下:

首先,创建一个类来实现该拦截器:

复制代码
public class PageInterceptor implements Interceptor {
 
    /**
     * 拦截方法
     *
     * @param invocation
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 获取原始的SQL语句
        String sql = (String) invocation.getArgs()[0];
        // 查询总数并计算出总页数和当前页
        int total = count(sql);
        // 如果总数小于等于0,则直接返回空结果集
        if (total <= 0) {
            return Collections.emptyList();
        }
        // 计算出当前页的起始位置和结束位置
        int offset = getOffset(pageNo, pageSize);
        int limit = pageSize;
        // 构造含分页信息的新SQL
        String newSql = getNewSql(sql, offset, limit);
        // 将新SQL替换成原来的SQL,并继续执行原有方法
        ReflectionUtils.setFieldValue(invocation, "h.sql", newSql);
        Object result = invocation.proceed();
        // 包装成Page对象,并返回
        Page<T> pageResult = new Page<>(pageNo,pageSize,total,(List<T>)result);
        return pageResult;
    }

    /**
     * 获取新的SQL语句(含分页信息)
     *
     * @param sql
     * @param offset
     * @param limit
     * @return
     */
    private String getNewSql(String sql, int offset, int limit) {
        return sql + " LIMIT " + offset + "," + limit;
    }

    /**
     * 获取查询结果总数
     *
     * @param sql
     * @return
     */
    private int count(String sql){
        // code omitted
    }

   /**
     * 计算当前分页的 Offset
     *
     * @param pageNo
     * @param pageSize
     * @return
     */
    private int getOffset(int pageNo, int pageSize) {
        return (pageNo - 1) * pageSize;
    }
}

然后,我们需要在 mybatis-config.xml 配置文件中注册该拦截器:

复制代码
<plugins>
        <plugin interceptor="com.example.mybatis.PageInterceptor"/>
</plugins>

最终,在查询数据时,我们便可以按照以下方式进行分页处理了:

复制代码
public List<User> selectUserListByPage(int startRow, int pageSize){
    RowBounds rowBounds = new RowBounds(startRow,pageSize);
    String statement = "com.example.UserMapper.selectUserList";
    return sqlSession.selectList(statement,null,rowBounds);
}

实现原理简述

  1. 注册拦截器:PageHelper插件通过MyBatis的插件机制注册为一个拦截器,主要拦截Executor接口的query方法。

  2. 动态修改SQL:拦截器在执行query方法前,检查是否有分页信息,如果有,则根据分页参数(如当前页、每页数量)修改SQL,添加分页限制。

  3. 数据库方言:考虑到不同的数据库有不同的分页语法,PageHelper支持多种数据库方言,会根据配置自动选择合适的分页实现。

  4. 执行并返回结果:修改后的SQL被执行,返回的结果是分页后的数据集。

查询电影数据案例
准备工作

首先,确保已经引入了PageHelper依赖,并在MyBatis的配置文件中配置了PageHelper插件,指定数据库方言,例如针对MySQL:

mybatis-config.xml:

复制代码
<configuration>
    <!-- ... -->
    <plugins>
        <plugin interceptor="com.github.pagehelper.PageInterceptor">
            <property name="helperDialect" value="mysql"/>
        </plugin>
    </plugins>
    <!-- ... -->
</configuration>

定义Mapper接口和映射文件

MovieMapper.java:

复制代码
public interface MovieMapper {
    List<Movie> selectMovies();
}

MovieMapper.xml:

复制代码
<mapper namespace="com.example.mapper.MovieMapper">
    <select id="selectMovies" resultType="com.example.entity.Movie">
        SELECT * FROM movie
    </select>
</mapper>
分页查询示例代码

在服务类中,使用PageHelper进行分页查询:

MovieService.java:

复制代码
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;

@Service
public class MovieService {
    @Autowired
    private MovieMapper movieMapper;

    public Page<Movie> getMoviesByPage(int pageNum, int pageSize) {
        // 开启分页
        PageHelper.startPage(pageNum, pageSize);
        
        // 执行查询,PageHelper会自动进行分页处理
        List<Movie> movies = movieMapper.selectMovies();
        
        // 返回分页对象,包含了分页信息和数据列表
        return (Page<Movie>) movies;
    }
}

在这个例子中,当调用getMoviesByPage方法时,首先通过PageHelper.startPage(pageNum, pageSize)设置分页信息。随后,当调用movieMapper.selectMovies()执行查询时,PageHelper拦截了这次查询请求,根据前面设置的分页参数动态地在SQL查询语句末尾添加了适合MySQL数据库的LIMIT和OFFSET子句,从而只获取当前页面所需的数据。最后,返回的是一个包含了当前页数据以及分页元数据(如总页数、总记录数等)的Page对象。

相关推荐
考虑考虑3 小时前
Jpa使用union all
java·spring boot·后端
用户3721574261354 小时前
Java 实现 Excel 与 TXT 文本高效互转
java
浮游本尊5 小时前
Java学习第22天 - 云原生与容器化
java
渣哥6 小时前
原来 Java 里线程安全集合有这么多种
java
间彧6 小时前
Spring Boot集成Spring Security完整指南
java
间彧7 小时前
Spring Secutiy基本原理及工作流程
java
Java水解8 小时前
JAVA经典面试题附答案(持续更新版)
java·后端·面试
洛小豆10 小时前
在Java中,Integer.parseInt和Integer.valueOf有什么区别
java·后端·面试
前端小张同学10 小时前
服务器上如何搭建jenkins 服务CI/CD😎😎
java·后端
ytadpole10 小时前
Spring Cloud Gateway:一次不规范 URL 引发的路由转发404问题排查
java·后端