你真的懂Mybatis分页原理吗?

目录

一、Mybatis如何实现分页?

[1.1 在SQL中添加limit语句](#1.1 在SQL中添加limit语句)

[1.2 基于PageHelper分页插件,实现分页](#1.2 基于PageHelper分页插件,实现分页)

[1.3 基于RowBounds实现分页](#1.3 基于RowBounds实现分页)

[1.4 基于MyBatis-Plus实现分页](#1.4 基于MyBatis-Plus实现分页)

二、RowBounds的分页原理

三、PageHelper的分页原理

四、Mybatis-Plus的分页原理

五、Mybatis-Plus实现分页

[5.1 添加分页插件](#5.1 添加分页插件)

[5.2 编写 Mapper 接口](#5.2 编写 Mapper 接口)

[5.3 执行分页查询](#5.3 执行分页查询)


一、Mybatis如何实现分页?

MyBatis可以通过两种方式来实现分页:基于物理分页 和基于逻辑分页

所谓物理分页,指的是最终执行的SQL中进行分页,即SQL语句中带limit,这样SQL语句执行之后返回的内容就是分页后的结果。

所谓逻辑分页,就是在SQL语句中不进行分页,照常全部查询,在查询到的结果集中,再进行分页。

在MyBatis中,想要实现分页通常有四种做法:

1.1 在SQL中添加limit语句

XML 复制代码
<select id="getUsers" resultType="User">
    select * from user 
    <where>
        <if test="name != null">
            and name like CONCAT('%',#{name},'%')
        </if>
    </where>
    limit #{offset}, #{limit}
</select>

1.2 基于PageHelper分页插件,实现分页

在使用PageHelper时,只需要在查询语句前调用PageHelper.startPage()方法,然后再进行查询操作。PageHelper会自动将查询结果封装到一个PageInfo对象中,包含了分页信息和查询结果。

java 复制代码
// Java代码中使用 PageHelper
PageHelper.startPage(1, 10);
List<User> userList = userMapper.getUsers();
PageInfo<User> pageInfo = new PageInfo<>(userList);

使用PageHelper插件,不需要在mapper.xml文件中使用limit语句

1.3 基于RowBounds实现分页

RowBounds是MyBatis中提供的一个分页查询工具,其中可以设置offset和limit用于分页。

java 复制代码
int offset = 10; // 偏移量
int limit = 5; // 每页数据条数
RowBounds rowBounds = new RowBounds(offset, limit);
List<User> userList = sqlSession.selectList("getUsers", null, rowBounds);

1.4 基于MyBatis-Plus实现分页

MyBatis-Plus提供了分页插件,可实现简单易用的分页功能,可以根据传入的分页参数自动计算出分页信息,无需手动编写分页SQL语句。

java 复制代码
public interface UserMapper extends BaseMapper<User> {
    List<User> selectUserPage(@Param("page") Page<User> page, @Param("name") String name);
}

以上四种做法中,能实现逻辑分页的是RowBounds和MyBatis-Plus,能实现物理分页的是手动添加limit、PageHelper以及MyBatis-Plus。

物理分页和逻辑分页,工作中推荐使用那种分页呢? 数据小的话无所谓,逻辑分页更简单点,数据量大的话,一定是物理分页,避免查询慢,也避免内存被撑爆。

二、RowBounds的分页原理

MyBatis的RowBounds是一个用于分页查询的简单POJO类,它包含两个属性offset和limit,分别表示分页查询的偏移量和每页查询的数据条数。

在使用RowBounds进行逻辑分页的时候,我们的SQL语句中是不需要指定分页参数的。就正常的查询即可,如:

XML 复制代码
<select id="getUsers" resultType="User">
  select * from user
  <where>
    <if test="name != null">
      and name like CONCAT('%',#{name},'%')
    </if>
  </where>
  order by id
</select>

然后,在查询的时候,将RowBounds当做一个参数传递:

java 复制代码
int offset = 10; // 偏移量
int limit = 5;   // 每页数据条数
RowBounds rowBounds = new RowBounds(offset, limit);
List<User> userList = sqlSession.selectList("getUserList", null, rowBounds);

这样,实际上在查询的时候,将会先所有符合条件的记录返回,然后再在内存中进行分页,分页的方式是根据RowBounds中指定的offset和limit进行数据保留,即抛弃掉不需要的数据再返回。

三、PageHelper的分页原理

PageHelper是MyBatis中提供的分页插件,主要是用来做物理分页的。

当我们在代码中使用 PageHelper.startPage(int pageNum, int pageSize) 设置分页参数之后,其实PageHelper会把它们存储到ThreadLocal中。

PageHelper会在执行翻的query方法执行之前,会从ThreadLocal中获取取分页参数信息,页码和页大小,然后执行分页查询,计算需要返回的数据的起始位置和大小。最后,PageHelper会通过修改SQL语句的方式,在SQL后面加上带有limit语句,限定查询的数据范围,从而实现物理分页的效果。而且在查询结束后再清除ThreadLocal中的分页参数。

四、Mybatis-Plus的分页原理

MyBatis-Plus支持分页插件------PaginationInnerInterceptor

PaginationInnerInterceptor采用的是物理分页方式,物理分页是在数据库中进行分页,即直接在SQL语句中加入LIMIT语句,只查询所需的部分数据。

物理分页的优点是可以减少内存占用,减轻数据库的负载,缺点是无法对结果进行任意操作,比如说在分页过程中做二次过滤、字段映射、json解析等。

PaginationInnerInterceptor这个分页插件就会自动拦截所有的SQL查询请求,计算分页查询的起始位置和记录数,并在 SQL语句中加入LIMIT语句。

核心的操作在beforeQuery中:

java 复制代码
@Override
public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    IPage<?> page = ParameterUtils.findPage(parameter).orElse(null);
    if (null == page) {
        return;
    }

    // 处理 orderBy 拼接
    boolean addOrdered = false;
    String buildSql = boundSql.getSql();
    List<OrderItem> orders = page.orders();
    if (CollectionUtils.isNotEmpty(orders)) {
        addOrdered = true;
        buildSql = this.concatOrderBy(buildSql, orders);
    }

    // size 小于 0 且不限制返回值则不构造分页sql
    Long _limit = page.maxLimit() != null ? page.maxLimit() : maxLimit;
    if (page.getSize() < 0 && null == _limit) {
        if (addOrdered) {
            PluginUtils.mpBoundSql(boundSql).sql(buildSql);
        }
        return;
    }

    handlerLimit(page, _limit);
    IDialect dialect = findDialect(executor);

    final Configuration configuration = ms.getConfiguration();
    DialectModel model = dialect.buildPaginationSql(buildSql, page.offset(), page.getSize(), _limit);
    PluginUtils.MPBoundSql mpBoundSql = PluginUtils.mpBoundSql(boundSql);

    List<ParameterMapping> mappings = mpBoundSql.parameterMappings();
    Map<String, Object> additionalParameter = mpBoundSql.additionalParameters();
    model.consumers(mappings, configuration, additionalParameter);
    mpBoundSql.sql(model.getDialectSql());
    mpBoundSql.parameterMappings(mappings);
}

其中比较关键的就是第31行,buildPaginationSql方法。这里不同的数据库有不同的实现,我们看一下MySQL的实现:

java 复制代码
public class MySqlDialect implements IDialect {

    @Override
    public DialectModel buildPaginationSql(String originalSql, long offset, long limit) {
        StringBuilder sql = new StringBuilder(originalSql).append(" LIMIT ").append(FIRST_MARK).append(COMMA);
        if (offset != 0L) {
            return new DialectModel(sql.toString(), offset, limit).setConsumerChain();
        } else {
            return new DialectModel(sql.toString(), limit).setConsumer(true);
        }
    }
}

这段代码就比较好理解了,其实就是在原来的SQL后面拼上 LIMIT ?,?,这样在后续执行的过程中,就可以把 offset和limit值给这两个占位符,实现分页查询了。

五、Mybatis-Plus实现分页

5.1 添加分页插件

在 Spring Boot 应用中,可以这样配置:

java 复制代码
@Configuration
@MapperScan("scan.your.mapper.package")
public class MybatisPlusConfig {

    /**
     * 添加分页插件
     */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); // 加入分页拦截器
        return interceptor;
    }
}

5.2 编写 Mapper 接口

定义一个 Mapper 接口,用于执行数据库操作。这个接口不需要特别指定分页相关的方法,MyBatis-Plus 会自动处理。

java 复制代码
public interface UserMapper extends BaseMapper<User> {
    // 这里可以添加其他数据库操作的方法
}

5.3 执行分页查询

在服务层或者控制器层,使用 MyBatis-Plus 提供的 Page 类来执行分页查询。例如,要查询第 1 页的数据,每页显示 10 条记录,可以这样写:

java 复制代码
@Autowired
private UserMapper userMapper;

public IPage<User> selectUserPage(int currentPage, int pageSize) {
    Page<User> page = new Page<>(currentPage, pageSize);
    IPage<User> userPage = userMapper.selectPage(page, null);
    return userPage;
}

selectPage 方法是 MyBatis-Plus 提供的内置方法,用于执行分页查询。null 作为第二个参数表示没有查询条件,即查询所有记录。

selectPage 方法返回的 IPage 对象包含了分页信息(如当前页码、总页数、每页记录数、总记录数等)和查询结果。

相关推荐
XiaoLeisj26 分钟前
【JavaEE初阶 — 多线程】单例模式 & 指令重排序问题
java·开发语言·java-ee
paopaokaka_luck28 分钟前
【360】基于springboot的志愿服务管理系统
java·spring boot·后端·spring·毕业设计
dayouziei28 分钟前
java的类加载机制的学习
java·学习
励志成为嵌入式工程师1 小时前
c语言简单编程练习9
c语言·开发语言·算法·vim
捕鲸叉2 小时前
创建线程时传递参数给线程
开发语言·c++·算法
A charmer2 小时前
【C++】vector 类深度解析:探索动态数组的奥秘
开发语言·c++·算法
Peter_chq2 小时前
【操作系统】基于环形队列的生产消费模型
linux·c语言·开发语言·c++·后端
Yaml42 小时前
Spring Boot 与 Vue 共筑二手书籍交易卓越平台
java·spring boot·后端·mysql·spring·vue·二手书籍
小小小妮子~2 小时前
Spring Boot详解:从入门到精通
java·spring boot·后端
hong1616882 小时前
Spring Boot中实现多数据源连接和切换的方案
java·spring boot·后端