分页+条件查询 + 批量删除
优化点:
- 后端:Query参数校验、Wrapper抽取、判空工具统一、逻辑删除适配、参数默认值、注解;
- DTO/Query设计:不复用User实体,独立UserQuery,存放分页+查询条件;
- 前端Vue3+Element‑Plus:TS类型、边界判断、loading防重复提交、清空选中、错误处理、类型安全;
- 接口补充:批量删除接口,MyBatis‑Plus逻辑删除适配;
//Page(当前页,每页条数)
IPage<User> page = new Page<>(1,5);
QueryWrapper<User> qw = new QueryWrapper<>();
qw.gt("age",22);
IPage<User> result = userMapper.selectPage(page, qw);
result.getRecords(); //当前页数据 List<User>
result.getTotal(); //总条数 Long
result.getPages(); //总页数 Long
result.getCurrent();//当前页码 Long
result.getSize(); //每页大小 Long
##
| 方法 | 返回类型 | 含义 |
|---|---|---|
getRecords() |
List<T> |
当前页查询出来的数据集合 |
getTotal() |
Long |
满足条件的全部总记录数 |
getPages() |
Long |
总页数,total / size向上取整 |
getCurrent() |
Long |
当前请求的页码 |
getSize() |
Long |
每页显示条数 |
1.
new Page<>(1,5):第一页,每页5条;MyBatis‑Plus页码从1开始,不是0。
2.selectPage(page,queryWrapper):
– page对象既是入参,也是出参;查询之后,total、pages这些值会回填到这个page对象。
3. 必须开启Mybatis‑Plus分页插件,否则分页失效,只会查询全部数据,不会统计total。
分页插件配置(SpringBoot)
@Configuration
public class MyBatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 添加分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
底层SQL执行两条SQL
- 查询当前页数据:
SELECT id,name,age... FROM user WHERE age > 22 LIMIT 0,5
- 自动执行count统计总条数:
SELECT COUNT(*) FROM user WHERE age > 22
如果不想执行count统计总条数:
page.setSearchCount(false); //关闭count查询,getTotal()返回0
Controller 返回示例(和你项目Result封装对齐)
@GetMapping("/list")
public Result<IPage<User>> list(){
IPage<User> page = new Page<>(1,5);
QueryWrapper<User> qw = new QueryWrapper<>();
qw.gt("age",22);
IPage<User> result = userMapper.selectPage(page, qw);
return Result.ok(result);
}
前端拿到后:
data.records:列表数据data.total:总条数,给el‑pagination的:total使用
实现步骤
Page(current,size)构造分页对象,传入selectPage;
插件拦截生成limit分页SQL + count统计;
返回IPage,records是本页数据,total总记录,pages总页数。
后端部分
UserQuery 查询DTO(独立POJO,不要复用User实体)
import com.baomidou.mybatisplus.annotation.TableField;
import lombok.Data;
@Data
public class UserQuery {
// 分页参数
private Long page = 1L;
private Long limit = 10L;
// 查询条件
private String name;
private String email;
}
Controller 层
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
@RestController
@RequestMapping("/user")
public class UserController {
@Resource
private IUserService iUserService;
/**
* 条件分页查询
*/
@GetMapping
public Result<IPage<User>> selectAll(UserQuery userQuery){
IPage<User> page = iUserService.listPage(userQuery);
return Result.ok(page);
}
/**
* 批量删除
*/
@DeleteMapping("/batch")
public Result<Void> batchRemove(@RequestBody List<Long> ids){
if(ids == null || ids.isEmpty()){
return Result.fail("请选择待删除数据");
}
boolean ok = iUserService.batchRemove(ids);
return ok ? Result.ok() : Result.fail("删除失败");
}
}
Service接口 IUserService
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import java.util.List;
public interface IUserService extends IService<User> {
IPage<User> listPage(UserQuery userQuery);
boolean batchRemove(List<Long> ids);
}
ServiceImpl实现类
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
@Resource
private UserMapper userMapper;
@Override
public IPage<User> listPage(UserQuery userQuery) {
// 构建分页对象,给默认值防止前端不传
Long pageNum = ObjectUtils.isNotEmpty(userQuery.getPage()) ? userQuery.getPage() : 1L;
Long pageSize = ObjectUtils.isNotEmpty(userQuery.getLimit()) ? userQuery.getLimit() : 10L;
IPage<User> page = new Page<>(pageNum, pageSize);
QueryWrapper<User> wrapper = new QueryWrapper<>();
// 条件拼接
wrapper.like(ObjectUtils.isNotEmpty(userQuery.getName()), "name", userQuery.getName());
wrapper.like(ObjectUtils.isNotEmpty(userQuery.getEmail()), "email", userQuery.getEmail());
// 排序扩展,可放到UserQuery
wrapper.orderByDesc("create_time");
return userMapper.selectPage(page, wrapper);
}
@Override
public boolean batchRemove(List<Long> ids) {
// ✅逻辑删除:使用 removeByIds,开启自动填充,不要直接mapper.deleteBatchIds
// 物理删除追求性能:removeByIds(ids, false)
return this.removeByIds(ids);
}
}
重点提醒:
– 如果开启@TableLogic逻辑删除,必须用this.removeByIds(ids),不要直接调用mapper的deleteBatchIds,不会自动填充删除时间、操作人。
–removeByIds(ids,false)才会生成IN(?)单条SQL,关闭自动填充。
前端 Vue3 + Element‑Plus(setup语法糖)
api/user.js
import request from '@/utils/request'
// 分页条件查询
export function getUserPage(params) {
return request({
url: '/user',
method: 'get',
params
})
}
// 批量删除
export function deleteAll(ids: number[]) {
return request({
url: '/user/batch',
method: 'delete',
data: ids
})
}
vue页面完整脚本部分
<template>
<div>
<!-- 分页查询 -->
<el-table
:data="users"
stripe
style="width: 100%"
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" width="55" />
<!-- 其他列 -->
</el-table>
<!-- 批量删除 -->
<el-pagination
v-model:current-page="userQuery.page"
v-model:page-size="userQuery.limit"
:page-sizes="[2, 5, 10]"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
@size-change="search"
@current-change="search"
/>
<el-button type="danger" @click="deleteAll" :loading="delLoading">批量删除</el-button>
</div>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { getUserPage, deleteAll } from '@/api/user'
// 查询对象:分页+查询条件全部放这里
const userQuery = reactive({
page: 1,
limit: 10,
name: '',
email: ''
})
//查询条件出来的所有对象
const users = ref([])
//总条数
const page = ref(0)
const ids = ref([])
const delLoading = ref(false)
// 选中行变化
const handleSelectionChange = (rows) => {
ids.value = rows.map(row => row.id)
}
// 查询刷新
function search(){
userApi.list(userQuery.value).then(response => {
users.value = response.data.records;
page.value = response.data.total;
})
}
// 批量删除
const deleteAll = async () => {
if (ids.value.length === 0) {
ElMessage.warning('请选择要删除的记录')
return
}
try {
await ElMessageBox.confirm('您确认要删除所选中的记录吗?', '警告', {
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning'
})
} catch {
// 用户点取消,直接return
return
}
delLoading.value = true
try {
const res = await deleteAll(ids.value)
ElMessage.success('删除成功')
// 删除后回到第一页刷新列表
userQuery.page = 1
await search()
// 清空表格选中状态
ids.value = []
} catch (err) {
ElMessage.error('删除失败')
} finally {
delLoading.value = false
}
}
// 页面初始化加载
search()
</script>
✨扩展知识点(面试/开发)
- 为什么不把page、limit放到User实体?
User是数据库实体,对应数据库字段;page/limit是前端查询参数,属于接口入参,职责分离,避免实体被污染。
removeByIds逻辑删除坑
逻辑删除不要直接调用
baseMapper.deleteBatchIds(ids),不会触发自动填充;
service.removeByIds(ids) 底层removeBatchByIds循环逐条删除,触发填充;
大批量id(>1000),建议自己手动分批切割集合。
- 前端细节
delLoading按钮loading,防止重复点击;- MessageBox捕获取消异常,不进入业务逻辑;
- 删除成功重置page=1,防止当前页数据删完空白;
- 清空ids,表格勾选状态清除。
- 后端扩展
- 可以在
UserQuery增加排序字段sortField、sortOrder,动态拼接orderBy; - 增加参数校验,
@Min(1)校验page、limit不能小于1; - 大数量批量删除,建议拆分集合分批删除。