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;
}
相关推荐
萧瑟余晖19 小时前
Java深入解析篇十七之Spring Security
java·开发语言·spring
Flittly1 天前
【雕虫大技】Agent 动态 Skill 供应链安全加固(二):执行隔离沙箱实战
java·spring boot·spring
元界metalite1 天前
MyBatis多数据源常见难题-事务开始了为何数据源还没选出来
后端·mybatis
霸道流氓气质1 天前
Mysql中慢 SQL 优化实战:减少不必要的 JOIN 关联
sql·mysql·mybatis
BUG指挥官2 天前
Sa-Token和Spring Security对比
java·后端·spring
geminigoth2 天前
Spring AI Alibaba 入门开发一(备份)
java·人工智能·spring
xbgRS2 天前
spring整合mybaits
java·spring·mybatis
ruleslol2 天前
静态依赖注入 VS 动态依赖查询
spring
墨雨晨曦882 天前
Spring AI总结
java·人工智能·spring
用户3126874877202 天前
Spring Boot 异常处理到底怎么玩的?从 DispatcherServlet 到全局兜底的全链路拆解
spring