Spring + MyBatis/MyBatis-Plus 分页方案(limit分页和游标分页)详解
版本说明
Spring Boot : 3.1.xMyBatis : 3.5.xMyBatis-Plus : 3.5.xPageHelper : 6.0.x
一、分页方式概述
1. 传统分页(LIMIT/OFFSET)
核心原理 :通过 LIMIT
和 OFFSET
截取数据片段适用场景 :后台管理系统、需跳页查询的场景实现方案 : 手动分页(原生 SQL) PageHelper 插件(推荐) MyBatis-Plus 分页插件(推荐)
2. 游标分页(Cursor-based)
核心原理 :基于排序字段游标(如 ID、时间戳)逐页查询适用场景 :移动端无限滚动、实时数据流实现方案 : 手动分页(主流方案) 成熟插件:目前无广泛采用的插件,需手动实现
二、传统分页实现(LIMIT/OFFSET)
方案 1:手动分页(原生 SQL)
请求参数类
public class PageParam { private Integer pageNum = 1 ; private Integer pageSize = 10 ; public Integer getOffset ( ) { return ( pageNum - 1 ) * pageSize; }
}
Mapper 接口
@Mapper
public interface UserMapper { List < User > selectByPage ( @Param ( "offset" ) Integer offset, @Param ( "pageSize" ) Integer pageSize) ; Long selectTotalCount ( ) ;
}
XML 映射文件
< select id = " selectByPage" resultType = " User" > SELECT * FROM userORDER BY id DESCLIMIT #{offset}, #{pageSize}
</ select>
< select id = " selectTotalCount" resultType = " java.lang.Long" > SELECT COUNT(*) FROM user
</ select>
Service 层
@Service
public class UserService { @Autowired private UserMapper userMapper; public PageResult < User > getUsers ( PageParam param) { List < User > users = userMapper. selectByPage ( param. getOffset ( ) , param. getPageSize ( ) ) ; Long total = userMapper. selectTotalCount ( ) ; return PageResult . < User > builder ( ) . list ( users) . total ( total) . pageNum ( param. getPageNum ( ) ) . pageSize ( param. getPageSize ( ) ) . build ( ) ; }
}
方案 2:PageHelper 插件(推荐)
配置插件
@Configuration
public class PageHelperConfig { @Bean public PageInterceptor pageInterceptor ( ) { PageInterceptor pageInterceptor = new PageInterceptor ( ) ; Properties props = new Properties ( ) ; props. setProperty ( "helperDialect" , "mysql" ) ; pageInterceptor. setProperties ( props) ; return pageInterceptor; }
}
Service 层使用
@Service
public class UserService { @Autowired private UserMapper userMapper; public PageInfo < User > getUsersByPage ( PageParam param) { PageHelper . startPage ( param. getPageNum ( ) , param. getPageSize ( ) ) ; List < User > users = userMapper. selectAll ( ) ; return new PageInfo < > ( users) ; }
}
Mapper 接口
@Mapper
public interface UserMapper { @Select ( "SELECT * FROM user" ) List < User > selectAll ( ) ;
}
方案 3:MyBatis-Plus 分页插件(推荐)
配置插件
@Configuration
public class MyBatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor ( ) { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor ( ) ; interceptor. addInnerInterceptor ( new PaginationInnerInterceptor ( DbType . MYSQL ) ) ; return interceptor; }
}
Service 层使用
@Service
public class UserService { @Autowired private UserMapper userMapper; public Page < User > getUsersByPage ( PageParam param) { Page < User > page = new Page < > ( param. getPageNum ( ) , param. getPageSize ( ) ) ; return userMapper. selectPage ( page, null ) ; }
}
Mapper 接口
public interface UserMapper extends BaseMapper < User > {
}
三、游标分页实现(手动分页)
1. 请求参数类
public class CursorParam { private Integer pageSize = 10 ; private Long lastCursor;
}
2. 响应参数类
public class CursorResult < T > { private List < T > list; private Boolean hasNext; private Long nextCursor;
}
3. Mapper 接口
@Mapper
public interface UserMapper { List < User > selectByCursor ( @Param ( "cursor" ) Long cursor, @Param ( "pageSize" ) Integer pageSize) ;
}
4. XML 映射文件
< select id = " selectByCursor" resultType = " User" > SELECT * FROM user< where> < if test = " cursor != null" > id < #{cursor}</ if> </ where> ORDER BY id DESCLIMIT #{pageSize}
</ select>
5. Service 层逻辑
@Service
public class UserService { @Autowired private UserMapper userMapper; public CursorResult < User > getUsersByCursor ( CursorParam param) { List < User > users = userMapper. selectByCursor ( param. getLastCursor ( ) , param. getPageSize ( ) + 1 ) ; CursorResult < User > result = new CursorResult < > ( ) ; boolean hasNext = users. size ( ) > param. getPageSize ( ) ; if ( hasNext) { result. setList ( users. subList ( 0 , param. getPageSize ( ) ) ) ; result. setNextCursor ( users. get ( param. getPageSize ( ) - 1 ) . getId ( ) ) ; } else { result. setList ( users) ; result. setNextCursor ( null ) ; } result. setHasNext ( hasNext) ; return result; }
}
四、分页插件对比
插件/方案 优点 缺点 适用场景 手动分页 完全控制 SQL 代码冗余,维护成本高 简单项目、特殊分页需求 PageHelper 零侵入,简单易用 依赖特定语法(PageHelper.startPage()
) 传统 MyBatis 项目 MyBatis-Plus 深度整合,支持 Lambda 表达式 需继承 BaseMapper MyBatis-Plus 项目 游标分页 高性能,无 OFFSET 无法跳页 移动端列表、实时数据流
五、注意事项
索引优化 :确保排序字段(如 id
)有索引安全限制 :限制最大 pageSize
(建议 ≤ 100)数据一致性 :分页期间数据变化可能导致结果差异参数校验 :校验 pageNum
≥1,pageSize
≥1
六、扩展建议
统一分页响应格式 :
public class R < T > { private Integer code; private String msg; private T data; private PageInfo page;
}
动态排序支持 :
< select id = " selectByPage" resultType = " User" > SELECT * FROM userORDER BY ${orderBy} ${orderDir}LIMIT #{offset}, #{pageSize}
</ select>
Redis 缓存优化 :
public Long getTotalCount ( ) { String cacheKey = "user:total" ; Long total = redisTemplate. opsForValue ( ) . get ( cacheKey) ; if ( total == null ) { total = userMapper. selectTotalCount ( ) ; redisTemplate. opsForValue ( ) . set ( cacheKey, total, 5 , TimeUnit . MINUTES ) ; } return total;
}