分页+条件查询 + 批量删除

分页+条件查询 + 批量删除

优化点:

  1. 后端:Query参数校验、Wrapper抽取、判空工具统一、逻辑删除适配、参数默认值、注解;
  1. DTO/Query设计:不复用User实体,独立UserQuery,存放分页+查询条件;
  1. 前端Vue3+Element‑Plus:TS类型、边界判断、loading防重复提交、清空选中、错误处理、类型安全;
  1. 接口补充:批量删除接口,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

  1. 查询当前页数据:
SELECT id,name,age... FROM user WHERE age > 22 LIMIT 0,5
  1. 自动执行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>

✨扩展知识点(面试/开发)

  1. 为什么不把page、limit放到User实体?

User是数据库实体,对应数据库字段;page/limit是前端查询参数,属于接口入参,职责分离,避免实体被污染。

  1. removeByIds逻辑删除坑

逻辑删除不要直接调用 baseMapper.deleteBatchIds(ids),不会触发自动填充;
service.removeByIds(ids) 底层removeBatchByIds循环逐条删除,触发填充;
大批量id(>1000),建议自己手动分批切割集合。

  1. 前端细节
  • delLoading按钮loading,防止重复点击;
  • MessageBox捕获取消异常,不进入业务逻辑;
  • 删除成功重置page=1,防止当前页数据删完空白;
  • 清空ids,表格勾选状态清除。
  1. 后端扩展
  • 可以在UserQuery增加排序字段 sortField、sortOrder,动态拼接orderBy;
  • 增加参数校验,@Min(1)校验page、limit不能小于1;
  • 大数量批量删除,建议拆分集合分批删除。
上一篇
下一篇