Spring + MyBatis/MyBatis-Plus 分页方案(limit分页和游标分页)详解

Spring + MyBatis/MyBatis-Plus 分页方案(limit分页和游标分页)详解

版本说明

  • Spring Boot: 3.1.x
  • MyBatis: 3.5.x
  • MyBatis-Plus: 3.5.x
  • PageHelper: 6.0.x

一、分页方式概述

1. 传统分页(LIMIT/OFFSET)

  • 核心原理 :通过 LIMITOFFSET 截取数据片段
  • 适用场景:后台管理系统、需跳页查询的场景
  • 实现方案
    • 手动分页(原生 SQL)
    • PageHelper 插件(推荐)
    • MyBatis-Plus 分页插件(推荐)

2. 游标分页(Cursor-based)

  • 核心原理:基于排序字段游标(如 ID、时间戳)逐页查询
  • 适用场景:移动端无限滚动、实时数据流
  • 实现方案
    • 手动分页(主流方案)
    • 成熟插件:目前无广泛采用的插件,需手动实现

二、传统分页实现(LIMIT/OFFSET)

方案 1:手动分页(原生 SQL)

请求参数类
java 复制代码
public class PageParam {
    private Integer pageNum = 1;  // 当前页码
    private Integer pageSize = 10; // 每页数量
    
    public Integer getOffset() {
        return (pageNum - 1) * pageSize;
    }
    // Getter & Setter
}
Mapper 接口
java 复制代码
@Mapper
public interface UserMapper {
    List<User> selectByPage(@Param("offset") Integer offset, 
                          @Param("pageSize") Integer pageSize);
    
    Long selectTotalCount();
}
XML 映射文件
xml 复制代码
<!-- 分页查询 -->
<select id="selectByPage" resultType="User">
    SELECT * FROM user
    ORDER BY id DESC
    LIMIT #{offset}, #{pageSize}
</select>

<!-- 总记录数 -->
<select id="selectTotalCount" resultType="java.lang.Long">
    SELECT COUNT(*) FROM user
</select>
Service 层
java 复制代码
@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 插件(推荐)

配置插件
java 复制代码
@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 层使用
java 复制代码
@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 接口
java 复制代码
@Mapper
public interface UserMapper {
    @Select("SELECT * FROM user")
    List<User> selectAll();
}

方案 3:MyBatis-Plus 分页插件(推荐)

配置插件
java 复制代码
@Configuration
public class MyBatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }
}
Service 层使用
java 复制代码
@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 接口
java 复制代码
public interface UserMapper extends BaseMapper<User> {
    // 继承 BaseMapper 自动获得分页能力
}

三、游标分页实现(手动分页)

1. 请求参数类

java 复制代码
public class CursorParam {
    private Integer pageSize = 10;    // 每页数量
    private Long lastCursor;         // 上一页最后记录的游标值
    
    // Getter & Setter
}

2. 响应参数类

java 复制代码
public class CursorResult<T> {
    private List<T> list;       // 当前页数据
    private Boolean hasNext;    // 是否有下一页
    private Long nextCursor;    // 下一页起始游标
    
    // Getter & Setter
}

3. Mapper 接口

java 复制代码
@Mapper
public interface UserMapper {
    List<User> selectByCursor(@Param("cursor") Long cursor, 
                             @Param("pageSize") Integer pageSize);
}

4. XML 映射文件

xml 复制代码
<select id="selectByCursor" resultType="User">
    SELECT * FROM user
    <where>
        <if test="cursor != null">
            id &lt; #{cursor}
        </if>
    </where>
    ORDER BY id DESC
    LIMIT #{pageSize}
</select>

5. Service 层逻辑

java 复制代码
@Service
public class UserService {
    @Autowired
    private UserMapper userMapper;

    public CursorResult<User> getUsersByCursor(CursorParam param) {
        // 实际查询 pageSize + 1 条用于判断是否有下一页
        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 无法跳页 移动端列表、实时数据流

五、注意事项

  1. 索引优化 :确保排序字段(如 id)有索引
  2. 安全限制 :限制最大 pageSize(建议 ≤ 100)
  3. 数据一致性:分页期间数据变化可能导致结果差异
  4. 参数校验 :校验 pageNum ≥1,pageSize ≥1

六、扩展建议

  1. 统一分页响应格式
java 复制代码
public class R<T> {
    private Integer code;
    private String msg;
    private T data;
    private PageInfo page; // 分页元数据
}
  1. 动态排序支持
xml 复制代码
<select id="selectByPage" resultType="User">
    SELECT * FROM user
    ORDER BY ${orderBy} ${orderDir}
    LIMIT #{offset}, #{pageSize}
</select>
  1. Redis 缓存优化
java 复制代码
// 缓存总记录数
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;
}
相关推荐
程序员Bears1 小时前
SSM整合:Spring+SpringMVC+MyBatis完美融合实战指南
java·spring·mybatis
zkmall8 小时前
B2C商城架构对比:ZKmall模板商城为何选择 Spring Cloud
spring·spring cloud·架构
shangjg39 小时前
MyBatis 动态 SQL 详解:灵活构建强大查询
java·数据库·架构·mybatis
王有品10 小时前
Spring 核心配置文件(spring.xml)构建指南
xml·java·spring
June56113 小时前
unix的定时任务和quartz和spring schedule的cron表达式区别
服务器·spring·unix
hstar952713 小时前
二十九、面向对象底层逻辑-SpringMVC九大组件之MultipartResolver接口设计
java·spring·设计模式·架构
hstar952713 小时前
三十、面向对象底层逻辑-SpringMVC九大组件之HandlerInterceptor接口设计
java·spring·设计模式·架构
王有品13 小时前
Spring MVC、Spring 与 MyBatis 整合详解
spring·mvc·mybatis
XiaoLeisj13 小时前
【博客系统】博客系统第五弹:基于令牌技术实现用户登录接口
java·javascript·spring boot·spring·java-ee·mybatis
結城14 小时前
如何用Spring Cache实现对Redis的抽象
java·redis·spring