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对象。

相关推荐
Scott9999HH3 小时前
【IIoT流量实战】蒸汽管道阀门全关却仍有流量?用 Python 实现涡街信号 FFT 频谱分析与温压全补偿积算网关,深度拆解靠谱的涡街流量计厂家硬核技术标准
开发语言·python
腻害兔3 小时前
【若依项目-产品经理视角】深度拆解 RuoYi-Vue-Pro 商城模块:从商品管理到交易引擎,50 张表撑起一整套电商系统
java·大数据·vue.js·产品经理·ai编程
码智社4 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海4 小时前
python 列表、元组、集合和字典
开发语言·python
萧瑟余晖5 小时前
JDK 26 新特性详解
java·开发语言
马优晨6 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
人邮异步社区8 小时前
怎么把C语言学到精通?
c语言·开发语言
心平气和量大福大8 小时前
C#-WPF-控件-TextBox 数据绑定
开发语言·c#·wpf
ttwuai8 小时前
Cursor 生成 CRUD 后,Go 后台接口别只测 200:JWT、RBAC 和 tenant_id 怎么验
开发语言·后端·golang
维天说9 小时前
CLI-Switch 2026年3月版历史设计:Hook、TTY 隔离与 JSON 状态
java·服务器·json