分页的理解
数据太多。用户并不需要这么多。我们的内存也放不下这么多的数据。
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中实现分页有两种实现
- 逻辑分页:RowBounds
- 物理分页:拦截器实现
RowBounds
逻辑分页是指在数据库中获取所有符合条件的记录,然后在内存中进行分页操作,而不是通过数据库的LIMIT
或OFFSET
进行物理分页。
要使用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 查询语句中使用数据库的 LIMIT
和 OFFSET
进行实现的。
编写拦截器类:
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
方法。在这个方法中,拦截了 Executor
的 query
方法,判断是否是分页查询,如果是,则修改原始 SQL,添加了 LIMIT
和 OFFSET
。
在MyBatis的配置文件中,配置拦截器:
<configuration>
<!-- 其他配置 -->
<plugins>
<plugin interceptor="your.package.name.PaginationInterceptor"/>
</plugins>
</configuration>
替换your.package.name
为实际的包名。
拦截器会在执行查询之前修改SQL,实现物理分页。需要注意的是,在物理分页中,LIMIT
和OFFSET
的值是直接传递给数据库的,因此在处理大量数据时可能会更加高效。