MyBatis中的分页原理

分页的理解

数据太多。用户并不需要这么多。我们的内存也放不下这么多的数据。

SQL:
MySQL: limit
Oracle: rowid

1. MySQL中的limit

MySQL中,LIMIT子句用于限制从查询中检索的行的数量。这对于分页非常有用,因为可以指定要检索的起始行和要返回的行数。通常,LIMIT的语法如下:

SELECT * FROM table_name
LIMIT [offset,] row_count;

其中:

  • offset 是从查询结果的起始位置开始的行数(可选)。默认情况下,偏移量为0,表示从第一行开始。
  • row_count 是要返回的行数。

例如,如果想要分页检索数据,每页显示10行,并希望显示第三页的数据,您可以使用以下查询:

SELECT * FROM your_table
LIMIT 20, 10;

这将从结果的第21行开始返回10行,从而实现第三页的效果。

要计算偏移量,可以使用以下公式:

offset = (page_number - 1) * page_size;

其中 page_number 是要检索的页数,page_size 是每页的行数。

请注意,LIMIT语句的性能可能会在大数据集上有一些问题,因为它需要数据库为每个查询计算并跳过指定数量的行。在大型分页查询的情况下,可能会考虑使用其他优化策略,例如基于游标的分页。

2. Oracle中的rowid

举例说明:

SELECT *
FROM (
    SELECT your_table.*, ROWID as my_rowid
    FROM your_table
    WHERE your_conditions
    ORDER BY your_ordering_columns
)
WHERE my_rowid BETWEEN :start_rowid AND :end_rowid;
  • your_table 是要查询的表。
  • your_conditions 是查询条件。
  • your_ordering_columns是用于排序的列。分页的关键在于排序,确保您有一个稳定的排序顺序。

在应用中,可以通过计算 :start_rowid 和 :end_rowid 的值来实现分页,这样就可以在每次查询时指定要返回的行范围。

请注意,使用 ROWID 分页的一个前提是,表中的行不会在分页查询的过程中发生删除或插入。如果表的内容可能在分页查询期间发生更改,那么使用 ROWID 可能不是最安全的选择,您可能需要考虑其他分页方法。

谈谈MyBatis中的分页实现

在MyBatis中实现分页有两种实现

  1. 逻辑分页:RowBounds
  2. 物理分页:拦截器实现

RowBounds

逻辑分页是指在数据库中获取所有符合条件的记录,然后在内存中进行分页操作,而不是通过数据库的LIMITOFFSET进行物理分页。

要使用RowBounds进行逻辑分页,可以在执行查询的时候传入一个RowBounds对象。以下是一个简答的示例:

import org.apache.ibatis.session.RowBounds;

//...

int offset = 0; // 起始行
int limit = 10; // 每页记录数

RowBounds rowBounds = new RowBounds(offset, limit);

List<MyEntity> resultList = sqlSession.selectList("yourNamespace.yourQuery", yourParameter, rowBounds);

在这个例子中,offset表示起始行,limit表示每页记录数。RowBounds将在内存中截取相应的结果集,实现逻辑分页。

在XML映射文件中,可以使用类似如下的SQL语句:

<select id="yourQuery" resultType="yourResultType">
    SELECT * FROM your_table
    WHERE your_condition
</select>

需要注意的是,逻辑分页是在查询结果集中进行截取的,因此可能会影响性能,特别是在返回大量数据时。在某些情况下,如果数据量很大,还是推荐使用数据库的物理分页来提高性能。

总体而言,RowBounds 是一种在 MyBatis 中实现逻辑分页的简单方式,但在处理大量数据时,需要谨慎考虑性能问题。

拦截器实现

物理分页是通过在 SQL 查询语句中使用数据库的 LIMITOFFSET 进行实现的。

编写拦截器类:

import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;

import java.util.Properties;

@Intercepts({
    @Signature(
        type = Executor.class,
        method = "query",
        args = {MappedStatement.class, Object.class, org.apache.ibatis.session.RowBounds.class, org.apache.ibatis.session.ResultHandler.class}
    )
})
public class PaginationInterceptor implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 获取原始的 MappedStatement
        MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];

        // 获取参数中的 RowBounds 对象
        org.apache.ibatis.session.RowBounds rowBounds = (org.apache.ibatis.session.RowBounds) invocation.getArgs()[2];

        // 如果是分页查询,则修改 SQL
        if (rowBounds instanceof PaginationRowBounds) {
            PaginationRowBounds paginationRowBounds = (PaginationRowBounds) rowBounds;

            // 修改原始 SQL,添加 LIMIT 和 OFFSET
            String originalSql = mappedStatement.getBoundSql(paginationRowBounds.getQuery()).getSql();
            String paginatedSql = originalSql + " LIMIT " + paginationRowBounds.getOffset() + ", " + paginationRowBounds.getLimit();

            // 修改 MappedStatement 中的 SQL
            MappedStatement newMappedStatement = copyFromMappedStatement(mappedStatement, new BoundSql(mappedStatement.getConfiguration(), paginatedSql, mappedStatement.getBoundSql(paginationRowBounds.getQuery()).getParameterMappings(), paginationRowBounds.getQuery().getParameterObject()));
            invocation.getArgs()[0] = newMappedStatement;
        }

        // 继续执行原始方法
        return invocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
        // 可以在这里设置一些属性,但在这个示例中并未使用
    }

    private MappedStatement copyFromMappedStatement(MappedStatement ms, BoundSql boundSql) {
        MappedStatement.Builder builder = new MappedStatement.Builder(ms.getConfiguration(), ms.getId(), new BoundSqlSqlSource(boundSql), ms.getSqlCommandType());
        builder.resource(ms.getResource());
        builder.fetchSize(ms.getFetchSize());
        builder.statementType(ms.getStatementType());
        builder.keyGenerator(ms.getKeyGenerator());
        builder.keyProperty(join(ms.getKeyProperties()));
        builder.timeout(ms.getTimeout());
        builder.parameterMap(ms.getParameterMap());
        builder.resultMaps(ms.getResultMaps());
        builder.cache(ms.getCache());
        builder.useCache(ms.isUseCache());
        return builder.build();
    }

    private static String join(String[] array) {
        StringBuilder builder = new StringBuilder();
        if (array != null && array.length > 0) {
            for (String item : array) {
                builder.append(item).append(",");
            }
            builder.delete(builder.length() - 1, builder.length());
        }
        return builder.toString();
    }

    private static class BoundSqlSqlSource implements SqlSource {
        private BoundSql boundSql;

        public BoundSqlSqlSource(BoundSql boundSql) {
            this.boundSql = boundSql;
        }

        @Override
        public BoundSql getBoundSql(Object parameterObject) {
            return boundSql;
        }
    }
}

在上述代码中,PaginationInterceptor实现了MyBatis的Interceptor接口,并重写了intercept方法。在这个方法中,拦截了 Executorquery 方法,判断是否是分页查询,如果是,则修改原始 SQL,添加了 LIMITOFFSET

在MyBatis的配置文件中,配置拦截器:

<configuration>
	<!-- 其他配置 -->

	<plugins>
		<plugin interceptor="your.package.name.PaginationInterceptor"/>
	</plugins>
</configuration>

替换your.package.name为实际的包名。

拦截器会在执行查询之前修改SQL,实现物理分页。需要注意的是,在物理分页中,LIMITOFFSET的值是直接传递给数据库的,因此在处理大量数据时可能会更加高效。

参考资料MyBatis刷题08:谈谈MyBatis中的分页原理?

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Mybatis分页原理可以通过深入了解Mybatis源码来理解。在Mybatis,有两种分页方式:逻辑分页和物理分页。 逻辑分页是通过设置RowBounds来实现的。RowBounds是Mybatis提供的一个类,用于描述分页参数,包括页码和每页显示数量。在进行数据库查询时,Mybatis会根据设置的RowBounds参数来返回相应的分页结果。这种方式适用于小数据量的分页查询。 物理分页是通过在Mapper文件编写分页语句来实现的。在Mapper文件,我们可以使用不同数据库类型的分页语句,比如MySQL和PostgreSQL。这些分页语句会在数据库层面进行分页操作,返回指定页码和数量的结果。这种方式适用于大数据量的分页查询。 除了以上两种方式,Mybatis还提供了分页插件来实现分页功能。其最常用的是PageHelper插件,它可以简化分页操作并支持多种数据库类型。该插件的实现原理是在调用查询前,通过ThreadLocal创建一个包含分页参数的Page对象,并将该对象存储在ThreadLocal。然后在查询过程,根据Page对象的参数进行分页处理。 总结来说,Mybatis分页原理包括逻辑分页和物理分页两种方式,可以根据具体需求选择合适的方式来实现分页操作。同时,分页插件如PageHelper可以简化分页操作并提供多种数据库支持。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.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^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *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^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值