MyBatis-Plus分页插件:一行代码搞定分页查询

告别繁琐的手写分页SQL,轻松实现高性能分页功能!

目录

🔧 配置分页插件

📚 基本分页查询

使用BaseMapper

使用IService

🔍 带条件的分页查询

🔄 自定义分页查询

1. 定义Mapper方法

2. 编写XML映射

3. 使用自定义分页

⚙️ 高级配置

溢出总页数处理

单页限制条数

优化Count查询

🖥️ 前端分页应用

🎨 前端表单实现示例

🎯 实战案例

案例:带复杂条件的用户分页查询

📝 小结

⏭️ 下一步学习


🔧 配置分页插件

MyBatis-Plus的分页功能需要先配置才能使用:

@Configuration
public class MybatisPlusConfig {
    
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        
        // 添加分页插件
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        
        return interceptor;
    }
}

⚠️ 注意:DbType要选择你使用的数据库类型,如MySQL、Oracle等。不同数据库的分页语法不同,插件会自动适配。

📚 基本分页查询

使用BaseMapper

// 1. 创建Page对象,指定当前页和每页记录数
Page<User> page = new Page<>(1, 10);  // 第1页,每页10条
​
// 2. 执行分页查询
Page<User> userPage = userMapper.selectPage(page, null);
​
// 3. 获取分页结果
List<User> users = userPage.getRecords();  // 当前页数据
long total = userPage.getTotal();          // 总记录数
long pages = userPage.getPages();          // 总页数
long current = userPage.getCurrent();      // 当前页
long size = userPage.getSize();            // 每页记录数

使用IService

// 使用IService接口的page方法
Page<User> page = new Page<>(1, 10);
Page<User> userPage = userService.page(page, null);

💡 提示:Page对象既是输入参数又是输出结果,查询完成后会自动填充总记录数、总页数等信息。

🔍 带条件的分页查询

结合条件构造器,实现带条件的分页查询:

// 创建Page对象
Page<User> page = new Page<>(1, 10);
​
// 创建条件构造器
LambdaQueryWrapper<User> query = Wrappers.<User>lambdaQuery();
query.gt(User::getAge, 20)
     .like(User::getName, "张");
​
// 执行分页查询
Page<User> userPage = userMapper.selectPage(page, query);

🔄 自定义分页查询

对于复杂的分页查询(如多表关联),可以自定义分页方法:

1. 定义Mapper方法

@Mapper
public interface UserMapper extends BaseMapper<User> {
    
    /**
     * 自定义分页查询
     */
    IPage<UserVO> selectUserPage(Page<UserVO> page, @Param("age") Integer age);
}

2. 编写XML映射

<mapper namespace="com.example.mapper.UserMapper">
    <select id="selectUserPage" resultType="com.example.vo.UserVO">
        SELECT u.id, u.name, u.age, u.email, d.name as deptName
        FROM user u
        LEFT JOIN dept d ON u.dept_id = d.id
        <where>
            <if test="age != null">
                AND u.age > #{age}
            </if>
        </where>
    </select>
</mapper>

3. 使用自定义分页

// 创建Page对象
Page<UserVO> page = new Page<>(1, 10);
​
// 执行自定义分页查询
IPage<UserVO> userPage = userMapper.selectUserPage(page, 20);
​
// 获取分页结果
List<UserVO> users = userPage.getRecords();

⚙️ 高级配置

溢出总页数处理

PaginationInnerInterceptor paginationInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
// 设置请求的页面大于最大页后操作
// true: 返回首页数据, false: 返回空数据
paginationInterceptor.setOverflow(false);

单页限制条数

// 设置最大单页限制条数,防止大查询
paginationInterceptor.setMaxLimit(100L);

优化Count查询

// 开启count查询优化,只针对部分 left join
paginationInterceptor.setOptimizeJoin(true);

🖥️ 前端分页应用

在实际项目中,我们通常需要将分页结果返回给前端:

@Data
public class PageResult<T> {
    private long current;    // 当前页码
    private long size;       // 每页记录数
    private long total;      // 总记录数
    private long pages;      // 总页数
    private List<T> records; // 数据列表
    
    // 转换方法
    public static <T> PageResult<T> convert(Page<T> page) {
        PageResult<T> result = new PageResult<>();
        result.setCurrent(page.getCurrent());
        result.setSize(page.getSize());
        result.setTotal(page.getTotal());
        result.setPages(page.getPages());
        result.setRecords(page.getRecords());
        return result;
    }
}

Controller中使用:

@GetMapping("/users")
public PageResult<User> getUserPage(
        @RequestParam(defaultValue = "1") long current,
        @RequestParam(defaultValue = "10") long size,
        @RequestParam(required = false) String name) {
    
    Page<User> page = new Page<>(current, size);
    LambdaQueryWrapper<User> query = Wrappers.<User>lambdaQuery();
    
    if (StringUtils.isNotBlank(name)) {
        query.like(User::getName, name);
    }
    
    Page<User> userPage = userService.page(page, query);
    return PageResult.convert(userPage);
}

🎨 前端表单实现示例

⚠️ 请注意: 下面的代码是一个 前端Vue组件的示例,用于演示如何与后端API交互。它 不能 直接在Markdown预览中作为交互式表单运行。您需要将此代码集成到您的Vue.js项目中,并安装相关依赖(如 axios)才能看到实际效果。

下面是一个使用Vue实现的带搜索和分页功能的表单示例:

<template>
  <div class="user-container">
    <!-- 搜索表单 -->
    <div class="search-form">
      <form @submit.prevent="handleSearch">
        <div class="form-item">
          <label for="name">姓名:</label>
          <input type="text" id="name" v-model="searchForm.name" placeholder="请输入姓名关键字">
        </div>
        <div class="form-item">
          <label for="minAge">最小年龄:</label>
          <input type="number" id="minAge" v-model="searchForm.minAge" placeholder="最小年龄">
        </div>
        <div class="form-item">
          <label for="maxAge">最大年龄:</label>
          <input type="number" id="maxAge" v-model="searchForm.maxAge" placeholder="最大年龄">
        </div>
        <div class="form-item">
          <label for="gender">性别:</label>
          <select id="gender" v-model="searchForm.gender">
            <option value="">全部</option>
            <option value="1">男</option>
            <option value="2">女</option>
          </select>
        </div>
        <div class="form-buttons">
          <button type="submit" class="btn-search">搜索</button>
          <button type="button" class="btn-reset" @click="resetForm">重置</button>
        </div>
      </form>
    </div>
​
    <!-- 数据表格 -->
    <table class="data-table">
      <thead>
        <tr>
          <th>ID</th>
          <th>姓名</th>
          <th>年龄</th>
          <th>性别</th>
          <th>邮箱</th>
          <th>部门</th>
          <th>操作</th>
        </tr>
      </thead>
      <tbody>
        <tr v-for="user in userList" :key="user.id">
          <td>{{ user.id }}</td>
          <td>{{ user.name }}</td>
          <td>{{ user.age }}</td>
          <td>{{ user.gender === 1 ? '男' : '女' }}</td>
          <td>{{ user.email }}</td>
          <td>{{ user.deptName }}</td>
          <td>
            <button @click="viewUser(user)">查看</button>
            <button @click="editUser(user)">编辑</button>
          </td>
        </tr>
        <tr v-if="userList.length === 0">
          <td colspan="7" class="no-data">暂无数据</td>
        </tr>
      </tbody>
    </table>
​
    <!-- 分页组件 -->
    <div class="pagination">
      <button :disabled="pagination.current === 1" @click="changePage(1)">首页</button>
      <button :disabled="pagination.current === 1" @click="changePage(pagination.current - 1)">上一页</button>
      <span class="page-info">
        {{ pagination.current }} / {{ pagination.pages }} 页,共 {{ pagination.total }} 条
      </span>
      <button :disabled="pagination.current >= pagination.pages" @click="changePage(pagination.current + 1)">下一页</button>
      <button :disabled="pagination.current >= pagination.pages" @click="changePage(pagination.pages)">末页</button>
      <select v-model="pagination.size" @change="handleSizeChange">
        <option :value="10">10条/页</option>
        <option :value="20">20条/页</option>
        <option :value="50">50条/页</option>
        <option :value="100">100条/页</option>
      </select>
    </div>
  </div>
</template>
​
<script>
import axios from 'axios';
​
export default {
  data() {
    return {
      // 搜索表单
      searchForm: {
        name: '',
        minAge: '',
        maxAge: '',
        gender: ''
      },
      // 用户列表
      userList: [],
      // 分页信息
      pagination: {
        current: 1,
        size: 10,
        total: 0,
        pages: 0
      }
    };
  },
  created() {
    this.fetchUserList();
  },
  methods: {
    // 获取用户列表
    async fetchUserList() {
      try {
        const params = {
          current: this.pagination.current,
          size: this.pagination.size,
          ...this.searchForm
        };
        
        const response = await axios.get('/api/users/search', { params });
        const result = response.data;
        
        this.userList = result.records;
        this.pagination.current = result.current;
        this.pagination.size = result.size;
        this.pagination.total = result.total;
        this.pagination.pages = result.pages;
      } catch (error) {
        console.error('获取用户列表失败', error);
        this.$message.error('获取用户列表失败');
      }
    },
    
    // 搜索
    handleSearch() {
      this.pagination.current = 1; // 重置到第一页
      this.fetchUserList();
    },
    
    // 重置表单
    resetForm() {
      this.searchForm = {
        name: '',
        minAge: '',
        maxAge: '',
        gender: ''
      };
      this.handleSearch();
    },
    
    // 切换页码
    changePage(page) {
      this.pagination.current = page;
      this.fetchUserList();
    },
    
    // 修改每页条数
    handleSizeChange() {
      this.pagination.current = 1; // 重置到第一页
      this.fetchUserList();
    },
    
    // 查看用户
    viewUser(user) {
      // 实现查看用户详情的逻辑
      console.log('查看用户', user);
    },
    
    // 编辑用户
    editUser(user) {
      // 实现编辑用户的逻辑
      console.log('编辑用户', user);
    }
  }
};
</script>
​
<style scoped>
.user-container {
  width: 100%;
  max-width: 1200px;
  margin: 0 auto;
  padding: 20px;
}
​
.search-form {
  background: #f5f7fa;
  padding: 15px;
  border-radius: 4px;
  margin-bottom: 20px;
}
​
.search-form form {
  display: flex;
  flex-wrap: wrap;
  gap: 15px;
  align-items: flex-end;
}
​
.form-item {
  display: flex;
  flex-direction: column;
  min-width: 200px;
}
​
.form-item label {
  margin-bottom: 5px;
  font-weight: bold;
}
​
.form-item input, .form-item select {
  padding: 8px 12px;
  border: 1px solid #dcdfe6;
  border-radius: 4px;
}
​
.form-buttons {
  display: flex;
  gap: 10px;
}
​
.btn-search, .btn-reset {
  padding: 8px 15px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}
​
.btn-search {
  background-color: #409eff;
  color: white;
}
​
.btn-reset {
  background-color: #909399;
  color: white;
}
​
.data-table {
  width: 100%;
  border-collapse: collapse;
  margin-bottom: 20px;
}
​
.data-table th, .data-table td {
  padding: 12px 8px;
  text-align: left;
  border-bottom: 1px solid #ebeef5;
}
​
.data-table th {
  background-color: #f5f7fa;
  color: #606266;
}
​
.data-table button {
  padding: 4px 8px;
  margin-right: 5px;
  background-color: #409eff;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}
​
.data-table button:last-child {
  background-color: #67c23a;
}
​
.no-data {
  text-align: center;
  color: #909399;
  padding: 20px 0;
}
​
.pagination {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
}
​
.pagination button {
  padding: 6px 12px;
  border: 1px solid #dcdfe6;
  background-color: white;
  cursor: pointer;
  border-radius: 4px;
}
​
.pagination button:disabled {
  cursor: not-allowed;
  opacity: 0.6;
}
​
.pagination select {
  padding: 6px;
  border: 1px solid #dcdfe6;
  border-radius: 4px;
}
​
.page-info {
  color: #606266;
}
</style>

🔄 前后端交互流程

  1. 初始化加载:页面创建时调用fetchUserList方法,获取第一页数据

  2. 条件搜索:用户填写搜索表单并提交,前端将搜索条件与分页参数一起发送到后端

  3. 分页切换:用户点击分页按钮,更新当前页码并重新请求数据

  4. 每页条数:用户修改每页显示条数,重置到第一页并重新请求数据

这个前端实现与我们之前展示的后端Controller完美配合,通过RESTful API进行数据交互,实现了一个完整的分页查询功能。

🔬 分页原理

MyBatis-Plus分页插件的工作原理

  1. 拦截执行的SQL

  2. 先执行COUNT查询获取总记录数

    SELECT COUNT(*) FROM user WHERE age > 20

  3. 再执行分页查询获取当前页数据

    SELECT * FROM user WHERE age > 20 LIMIT 0, 10

  4. 将两次查询结果封装到Page对象中

🎯 实战案例

案例:带复杂条件的用户分页查询

@GetMapping("/users/search")
public PageResult<UserVO> searchUsers(UserSearchDTO searchDTO) {
    // 创建分页对象
    Page<User> page = new Page<>(searchDTO.getCurrent(), searchDTO.getSize());
    
    // 构建查询条件
    LambdaQueryWrapper<User> query = Wrappers.<User>lambdaQuery();
    
    // 动态添加查询条件
    query.like(StringUtils.isNotBlank(searchDTO.getName()), User::getName, searchDTO.getName())
         .ge(searchDTO.getMinAge() != null, User::getAge, searchDTO.getMinAge())
         .le(searchDTO.getMaxAge() != null, User::getAge, searchDTO.getMaxAge())
         .eq(searchDTO.getGender() != null, User::getGender, searchDTO.getGender())
         .between(
             searchDTO.getStartTime() != null && searchDTO.getEndTime() != null,
             User::getCreateTime,
             searchDTO.getStartTime(),
             searchDTO.getEndTime()
         );
    
    // 设置排序
    if (StringUtils.isNotBlank(searchDTO.getSortField())) {
        query.orderBy(true, 
                     "asc".equalsIgnoreCase(searchDTO.getSortOrder()), 
                     SFunction.parse(searchDTO.getSortField()));
    } else {
        query.orderByDesc(User::getCreateTime);
    }
    
    // 执行查询
    Page<User> userPage = userService.page(page, query);
    
    // 转换结果
    Page<UserVO> voPage = userPage.convert(user -> {
        UserVO vo = new UserVO();
        BeanUtils.copyProperties(user, vo);
        vo.setDeptName(getDeptName(user.getDeptId()));
        return vo;
    });
    
    return PageResult.convert(voPage);
}

📝 小结

MyBatis-Plus分页插件的优势:

优势说明
使用简单几行代码即可实现分页,无需手写SQL
无侵入不需要修改现有代码
高性能自动优化COUNT查询,提高查询效率
可扩展支持自定义分页查询,满足复杂业务需求

🔥 最佳实践:在Service层封装分页查询逻辑,Controller层只负责参数接收和结果返回,保持代码层次清晰。

⏭️ 下一步学习

<think>好的,我现在需要帮助用户查找MyBatisPlus分页查询的示例和使用方法。首先,用户的问题是关于MyBatisPlus分页查询的具体实现步骤,可能包括配置和代码示例。我应该先回忆一下MyBatisPlus分页机制,然后结合用户提供的引用内容来组织回答。 根据用户提供的引用,特别是引用[1]和[2],MyBatisPlus分页插件是通过拦截器实现的,需要配置分页插件,并且使用Page对象进行分页查询。引用中提到PageHelper和MyBatisPlus内置的分页插件,但需要注意两者可能有所不同,用户可能混淆了,需要明确说明MyBatisPlus自己的分页插件使用方法。 首先,用户需要引入MyBatisPlus的依赖,这可能包括在pom.xml中添加相关依赖,但用户提供的引用中并没有具体版本,可能需要我给出常用的依赖配置。不过引用[1]提到引入PageHelper,这可能是一个干扰项,因为PageHelper是另一个分页插件,而MyBatisPlus有自己的分页插件。需要明确区分,避免用户混淆。因此,在回答中应明确指出MyBatisPlus内置的分页插件,不需要PageHelper。 接下来,配置分页插件。根据引用[2],MyBatisPlus分页插件通过拦截器机制实现物理分页,需要在配置类中添加拦截器。这里需要给出具体的Java配置代码示例,比如使用@Configuration类,添加MybatisPlusInterceptor,并注册PaginationInnerInterceptor。 然后,实际使用分页查询。用户需要创建Page对象,调用selectPage方法,并处理结果。需要给出Service层或Mapper层的示例代码,比如在Service中调用baseMapper的selectPage方法,并返回Page对象。同时,需要说明Page对象包含的数据和分页信息,如总记录数、当前页等。 可能还需要提到自定义查询的分页处理,比如在XML中编写SQL不需要手动添加limit,因为插件会自动处理。这部分可以引用引用[2]中提到的物理分页机制。 最后,确保回答结构清晰,分步骤说明,并给出代码示例,同时引用正确的来源。需要检查是否有遗漏的步骤,比如依赖引入是否正确,配置是否完整,以及示例代码是否可运行。同时,用户可能遇到的问题,比如版本兼容性或配置错误,可能需要提及,但根据用户问题,可能不需要深入,保持回答简洁。 现在整理回答的结构:1. 引入依赖(正确MyBatisPlus依赖);2. 配置分页插件;3. 使用分页查询(示例代码);4. 自定义SQL的分页处理。每个步骤配合代码示例和引用说明。</think>MyBatisPlus 分页查询的实现主要分为以下步骤,结合物理分页机制和拦截器技术实现高效分页: ### 一、配置分页插件 1. **添加依赖**(以 Maven 为例) ```xml <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.0</version> </dependency> ``` 2. **注册分页拦截器** 在 Spring Boot 配置类中添加: ```java @Configuration public class MybatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor()); return interceptor; } } ``` 该拦截器会自动为 SQL 添加数据库方言的分页语句(如 MySQL 的 `LIMIT`)[^2]。 --- ### 二、分页查询使用示例 #### 基础用法(Service 层) ```java // 1. 创建分页参数(第2页,每页10条) Page<User> page = new Page<>(2, 10); // 2. 执行分页查询 Page<User> result = userService.page(page); // 3. 获取分页数据 List<User> records = result.getRecords(); // 当前页数据 long total = result.getTotal(); // 总记录数 ``` #### 条件分页查询(Mapper 层) ```java // 1. 构建查询条件 LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>(); wrapper.like(User::getName, "张"); // 2. 执行分页查询 Page<User> page = new Page<>(1, 5); userMapper.selectPage(page, wrapper); ``` --- ### 三、自定义 SQL 分页 在 XML 映射文件中无需手动写 `LIMIT`,插件会自动改写 SQL: ```xml <select id="selectUserByAge" resultType="User"> SELECT * FROM user WHERE age > #{age} </select> ``` Java 调用: ```java Page<User> page = new Page<>(1, 10); userMapper.selectUserByAge(page, 18); List<User> users = page.getRecords(); ``` --- ### 四、关键特性说明 1. **物理分页机制** 直接在数据库层面执行分页(如 MySQL 生成 `LIMIT 0,10`),避免内存溢出风险[^2]。 2. **性能优化** 自动检测数据库类型并适配方言,支持多种数据库(MySQL、Oracle、PostgreSQL 等)。 3. **智能 COUNT 查询** 若需要总记录数,插件会自动发起 `COUNT(*)` 查询;若已知总数可调用 `page.setSearchCount(false)` 跳过统计。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值