你真的懂Mybatis分页原理吗?

目录

一、Mybatis如何实现分页?

1.1 在SQL中添加limit语句

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

1.3 基于RowBounds实现分页

1.4 基于MyBatis-Plus实现分页

二、RowBounds的分页原理

三、PageHelper的分页原理

四、Mybatis-Plus的分页原理

五、Mybatis-Plus实现分页

5.1 添加分页插件

5.2 编写 Mapper 接口

5.3 执行分页查询


一、Mybatis如何实现分页?

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

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

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

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

1.1 在SQL中添加limit语句

<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代码中使用 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用于分页。

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语句。

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语句中是不需要指定分页参数的。就正常的查询即可,如:

<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当做一个参数传递:

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中:

@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的实现:

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 应用中,可以这样配置:

@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 会自动处理。

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

5.3 执行分页查询

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

@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 对象包含了分页信息(如当前页码、总页数、每页记录数、总记录数等)和查询结果。

Mybatis分页原理是通过使用RowBounds对象来实现逻辑分页和物理分页。在Mapper文件中,我们可以设置RowBounds对象来指定需要返回的分页结果。在底层实现中,Mybatis会根据RowBounds对象的参数来生成相应的SQL语句,从而实现分页功能。\[1\] 具体来说,当我们调用Mybatis的startPage方法时,会创建一个包含分页参数的Page对象,并将其放置在ThreadLocal中。Page对象包含了页码、每页显示数量等分页参数。然后,Mybatis会根据Page对象的参数生成相应的SQL语句,从数据库中查询出符合条件的分页结果。\[3\] 在物理分页中,Mybatis的Mapper文件中支持多种数据库类型的分页语句。根据不同的数据库类型,Mybatis会生成相应的分页SQL语句,以实现物理分页功能。\[2\] 总结起来,Mybatis分页原理是通过使用RowBounds对象和Page对象来指定分页参数,并根据这些参数生成相应的SQL语句,从而实现逻辑分页和物理分页功能。 #### 引用[.reference_title] - *1* *2* [一篇文章学通 Mybatis 分页相关知识](https://blog.csdn.net/valada/article/details/84933860)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [MyBatis分页原理](https://blog.csdn.net/web15085599741/article/details/123350219)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

全真王重阳

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值