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)

请求参数类
public class PageParam {
    private Integer pageNum = 1;  // 当前页码
    private Integer pageSize = 10; // 每页数量
    
    public Integer getOffset() {
        return (pageNum - 1) * pageSize;
    }
    // Getter & Setter
}
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 user
    ORDER BY id DESC
    LIMIT #{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> {
    // 继承 BaseMapper 自动获得分页能力
}

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

1. 请求参数类

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

2. 响应参数类

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

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 &lt; #{cursor}
        </if>
    </where>
    ORDER BY id DESC
    LIMIT #{pageSize}
</select>

5. Service 层逻辑

@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 表达式需继承 BaseMapperMyBatis-Plus 项目
游标分页高性能,无 OFFSET无法跳页移动端列表、实时数据流

五、注意事项

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

六、扩展建议

  1. 统一分页响应格式
public class R<T> {
    private Integer code;
    private String msg;
    private T data;
    private PageInfo page; // 分页元数据
}
  1. 动态排序支持
<select id="selectByPage" resultType="User">
    SELECT * FROM user
    ORDER BY ${orderBy} ${orderDir}
    LIMIT #{offset}, #{pageSize}
</select>
  1. 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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值