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