Spring3整合MyBatis实现增删改查操作

上节博客我们讲了实现分页查询和搜索功能,这篇博客我们继续完成增删改查操作,在前后端分离开发模式中,用户数据的增删改查(CRUD)是最基础也最核心的业务场景。本文将详细介绍如何结合 MyBatis 后端框架与 Vue 前端技术栈,搭配 Element UI 组件库,实现一套功能完善、交互流畅的用户数据管理系统,涵盖表单验证、分页查询、批量操作等关键功能,为开发同类系统提供清晰的实现思路。

一、实现新增数据

1.UserMapper.xml

在 MyBatis 的映射文件中,通过<insert>标签编写插入 SQL,指定要插入的字段(username、birthday等),并通过#{字段名}接收实体类参数:

2. Controller 层:接收前端 JSON 数据

前端传递的是 JSON 格式的表单数据,所以后端需要用@RequestBody注解接收(将 JSON 自动转为 User 实体类),再调用 Service 层的insert方法完成入库:

复制代码
<el-dialog title="信息" v-model="data.formVisible" width="30%" destroy-on-close>
      <el-form ref="formRef" :model="data.form" :rules="data.rules" label-width="80px" style="padding: 20px 30px 10px 0">
        <el-form-item prop="username" label="账号">
          <el-input v-model="data.form.username" autocomplete="off" />
        </el-form-item>
        <el-form-item prop="birthday" label="生日">
         <el-date-picker
                v-model="data.form.birthday"
                type="datetime"
                placeholder="选择日期时间"
          ></el-date-picker>
        </el-form-item>
        <el-form-item prop="sex" label="性别">
          <el-select v-model="data.form.sex" placeholder="请选择性别" style="width: 100%">
            <el-option label="男" value="男"></el-option>
            <el-option label="女" value="女"></el-option>
          </el-select>
        </el-form-item>
        <el-form-item prop="address" label="地址">
          <el-input v-model="data.form.address" autocomplete="off" />
        </el-form-item>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="data.formVisible = false">取 消</el-button>
          <el-button type="primary" @click="save">保 存</el-button>
        </div>
      </template>
</el-dialog>

3. 设置提交的表单

el-dialog(弹窗)包裹el-form,实现 "点击新增后弹出表单" 的交互;通过model绑定表单数据(data.form),rules配置表单验证规则:

4. 点击新增按钮实现弹框打开

给 "新增" 按钮绑定@click事件,触发handleAdd方法,将 "表单弹窗显示状态"(formVisible)设为true,同时清空之前的表单数据:

5.script数据提交(注意点)

通过reactive定义表单数据(form)、弹窗状态(formVisible)、验证规则(rules);点击 "提交" 时,先通过formRef做表单验证,验证通过后调用接口提交数据:

复制代码
<script setup>
import { reactive,ref } from "vue";
import {Search} from "@element-plus/icons-vue";
import request from "@/utils/request.js";
import {ElMessage} from "element-plus";

const data = reactive({
  name: null,
  pageNum: 1,
  pageSize: 5,
  total:0,
  tableData: [],
  formVisible: false, // 表单是否显示
  form: {}, //表单数据
  rules: {
    username: [
      { required: true, message: '请填写用户名', trigger: 'blur' }
    ],
    birthday: [
      { required: false, message: '请填写生日', trigger: 'blur' }
    ],
    sex: [
      { required: true, message: '请填写性别', trigger: 'blur' }
    ],
    address: [
      { required: true, message: '请填写地址', trigger: 'blur' }
    ]
  },
  rows: []
})

const formRef = ref() //表单实例


const load = () => {
  request.get('/user/selectPage', {
    //入参
    params: {
      pageNum: data.pageNum,
      pageSize: data.pageSize,
      username:data.username,
      address:data.address
    }
  }).then(res => {
    if (res.code === '200') {
      data.tableData = res.data.list
      data.total = res.data.total
    } else {
      ElMessage.error(res.msg)
    }
  })
}
load()

//搜索重置
const reset = () => {
  data.username = null
  data.address = null
  load()
}

//点击新增按钮触发的方法
const handleAdd = () => {
  data.formVisible = true //显示弹框
  data.form = {}  //清空表单
}

const add = () => {
  console.log(data.form);
  
  // formRef 是表单的引用
  formRef.value.validate((valid) => {
    if (valid) {   // 验证通过的情况下
      request.post('/user/add', data.form).then(res => {
        if (res.code === '200') {
          data.formVisible = false //隐藏弹框
          ElMessage.success('新增成功')
          load() //重新加载数据
        } else {
          ElMessage.error(res.msg)
        }
      })
    }
  })
}

//点击保存触发的方法
const save = () => {
  add()
}

</script>

二、实现修改数据

2.1 UserMapper.xml

用 MyBatis 的<set><if>标签实现动态更新 (只修改非空的字段),避免覆盖原有数据,同时通过where id = #{id}定位要修改的行:

复制代码
<update id="update" parameterType="com.qcby.springboot.entity.User">
    update user
    <set>
        <if test="username!=null">
            username=#{username},
        </if>
        <if test="birthday!=null">
            birthday=#{birthday},
        </if>
        <if test="sex!=null">
            sex=#{sex},
        </if>
        <if test="address!=null">
            address=#{address},
        </if>
    </set>
    where id = #{id}
</update>

2.2 controller层

@PostMapping("/update")定义修改接口,通过@RequestBody接收前端传递的(包含id的)User 对象,调用 Service 层的update方法完成修改:

2.3 设置vue的修改和删除按钮

el-table-column的 "操作" 列中,给修改按钮绑定@click="handleEdit(scope.row)",点击时把当前行数据(scope.row)传给handleEdit方法:

复制代码
import { Delete,Edit} from '@element-plus/icons-vue'

<el-table-column label="操作" width="100">
    <template #default="scope">
       <el-button type="primary" icon="Edit" circle @click="handleEdit(scope.row)"></el-button>
       <el-button type="danger" icon="Delete" circle @click="del(scope.row.id)"></el-button>
    </template>
</el-table-column>

2.4 设置vue的数据提交

handleEdit方法接收表格行数据后,通过JSON.parse(JSON.stringify(row))深度拷贝数据 (避免修改表单时直接影响表格原数据),再把拷贝后的数据赋值给data.form,同时打开弹窗(formVisible: true):

点击弹窗的 "保存" 按钮时,通过data.form.id是否存在,判断是 "新增" 还是 "修改":

  • id(修改):调用update()方法
  • id(新增):调用add()方法

update方法先做表单验证,验证通过后调用后端/user/update接口,把修改后的data.form(包含id)传给后端,成功后关闭弹窗并刷新表格:

复制代码
 //打开修改的弹框
 const handleEdit = (row)=>{
  data.form = JSON.parse(JSON.stringify(row))  // 深度拷贝数据
  data.formVisible = true
}

//点击保存触发的方法
const save = () => {
  data.form.id ? update() : add()
}
//修改
const update = () => {
  // formRef 是表单的引用
  formRef.value.validate((valid) => {
    if (valid) {   // 验证通过的情况下
      request.post('/user/update', data.form).then(res => {
        if (res.code === '200') {
          data.formVisible = false
          ElMessage.success('修改成功')
          load()
        } else {
          ElMessage.error(res.msg)
        }
      })
    }
  })
}

2.5 完整前端代码

复制代码
<template>
  <div>
    <div class="card" style="margin-bottom: 5px">
      <el-input clearable @clear="load" style="width: 260px; margin-right: 5px" v-model="data.username" placeholder="请输入用户名查询" :prefix-icon="Search"></el-input>
      <el-input clearable @clear="load" style="width: 260px; margin-right: 5px" v-model="data.address" placeholder="请输入地址查询" :prefix-icon="Search"></el-input>
      <el-button type="primary" @click="load">查 询</el-button>
      <el-button @click="reset">重 置</el-button>
    </div>
    <div class="card" style="margin-bottom: 5px">
      <el-button type="primary" @click="handleAdd">新 增</el-button>
      <el-button type="danger">批量删除</el-button>
      <el-button type="success">批量导入</el-button>
      <el-button type="info">批量导出</el-button>
    </div>
    
    <div class="card" style="margin-bottom: 5px">
    <el-table :data="data.tableData" style="width: 100%" :header-cell-style="{ color: '#333', backgroundColor: '#eaf4ff' }">
      <el-table-column type="selection" width="55" />
      <el-table-column prop="username" label="用户名" />
      <el-table-column prop="birthday" label="生日" />
      <el-table-column prop="sex" label="性别" />
      <el-table-column prop="address" label="地址" />
      <el-table-column label="操作" width="100">
          <template #default="scope">
            <el-button type="primary" icon="Edit" circle @click="handleEdit(scope.row)"></el-button>
            <el-button type="danger" icon="Delete" circle @click="del(scope.row.id)"></el-button>
          </template>
        </el-table-column>
    </el-table>
  </div>
  <div class="card">
    <el-pagination
        v-model:current-page="data.pageNum"  
        v-model:page-size="data.pageSize"
        layout="total, sizes, prev, pager, next, jumper"
        :page-sizes="[5, 10, 20]"
        :total="data.total"
        @current-change="load"
        @size-change="load"
    />
  </div>
</div>

<el-dialog title="信息" v-model="data.formVisible" width="30%" destroy-on-close>
      <el-form ref="formRef" :model="data.form" :rules="data.rules" label-width="80px" style="padding: 20px 30px 10px 0">
        <el-form-item prop="username" label="账号">
          <el-input v-model="data.form.username" autocomplete="off" />
        </el-form-item>
        <el-form-item prop="birthday" label="生日">
          <el-date-picker
                v-model="data.form.birthday"
                type="datetime"
                placeholder="选择日期时间"
          ></el-date-picker>
        </el-form-item>
        <el-form-item prop="sex" label="性别">
          <el-input v-model="data.form.sex" autocomplete="off" />
        </el-form-item>
        <el-form-item prop="address" label="地址">
          <el-input v-model="data.form.address" autocomplete="off" />
        </el-form-item>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="data.formVisible = false">取 消</el-button>
          <el-button type="primary" @click="save">保 存</el-button>
        </div>
      </template>
</el-dialog>


</template>

<script setup>
import { reactive,ref } from "vue";
import {Search} from "@element-plus/icons-vue";
import request from "@/utils/request.js";
import {ElMessage} from "element-plus";
 

const data = reactive({
  name: null,
  pageNum: 1,
  pageSize: 5,
  total:0,
  tableData: [],
  formVisible: false, // 表单是否显示
  form: {}, //表单数据
  rules: {
    username: [
      { required: true, message: '请填写用户名', trigger: 'blur' }
    ],
    birthday: [
      { required: true, message: '请填写生日', trigger: 'blur' }
    ],
    sex: [
      { required: true, message: '请填写性别', trigger: 'blur' }
    ],
    address: [
      { required: true, message: '请填写地址', trigger: 'blur' }
    ]
  },
  rows: []
})

const formRef = ref() //表单实例


const load = () => {
  request.get('/user/selectPage', {
    //入参
    params: {
      pageNum: data.pageNum,
      pageSize: data.pageSize,
      username:data.username,
      address:data.address
    }
  }).then(res => {
    if (res.code === '200') {
      data.tableData = res.data.list
      data.total = res.data.total
    } else {
      ElMessage.error(res.msg)
    }
  })
}
load()

//搜索重置
const reset = () => {
  data.username = null
  data.address = null
  load()
}

//点击新增按钮触发的方法
const handleAdd = () => {
  data.formVisible = true //显示弹框
  data.form = {}  //清空表单
}

const add = () => {
  console.log(data.form);
  
  // formRef 是表单的引用
  formRef.value.validate((valid) => {
    if (valid) {   // 验证通过的情况下
      request.post('/user/add', data.form).then(res => {
        if (res.code === '200') {
          data.formVisible = false //隐藏弹框
          ElMessage.success('新增成功')
          load() //重新加载数据
        } else {
          ElMessage.error(res.msg)
        }
      })
    }
  })
}

 //打开修改的弹框
 const handleEdit = (row)=>{
  data.form = JSON.parse(JSON.stringify(row))  // 深度拷贝数据
  data.formVisible = true
}

//点击保存触发的方法
const save = () => {
  data.form.id ? update() : add()
}
//修改
const update = () => {
  // formRef 是表单的引用
  formRef.value.validate((valid) => {
    if (valid) {   // 验证通过的情况下
      request.post('/user/update', data.form).then(res => {
        if (res.code === '200') {
          data.formVisible = false
          ElMessage.success('修改成功')
          load()
        } else {
          ElMessage.error(res.msg)
        }
      })
    }
  })
}
</script>

三、实现删除数据

3.1 xml配置

通过<delete>标签编写删除语句,根据id(前端传递的用户主键)定位并删除对应行:

3.2 controller层

@PostMapping("/delete/{id}")定义接口,通过@PathVariable接收 URL 路径中的id(比如请求/delete/10id就是 10),再调用 Service 层的delete方法执行删除:

3.3 删除数据

del方法先通过ElMessageBox.confirm弹出确认框(避免误删),用户确认后调用后端/delete/{id}接口,删除成功后刷新表格:

复制代码
//删除数据
const del = (id) => {
  ElMessageBox.confirm('删除后无法恢复,您确认删除吗?', '删除确认', { type: 'warning' }).then(res => {
    request.post('/user/delete/' + id).then(res => {
      if (res.code === '200') {
        ElMessage.success('删除成功')
        load()
      } else {
        ElMessage.error(res.msg)
      }
    })
  }).catch(err => {})
}

注意这里需要导入 ElMessageBox ElMessageBox 是 Element Plus UI 库提供的一个弹出框组件,主要用于显示各种类型的确认对话框、提示信息等。

复制代码
import {ElMessage, ElMessageBox } from "element-plus";

四、批量删除数据

4.1 UserMapper.xml

用 MyBatis 的<foreach>标签,把 ID 数组拼接成in (id1,id2,...)的格式,实现 "一次删除多行":

复制代码
<!--批量删除-->
<delete id="deleteMore" >
    delete from user where id in
    <foreach collection="ids" item="id" separator="," close=")" open="(">
        #{id}
    </foreach>
</delete>

4.2 controller层

@PostMapping("/delMore")定义批量删除接口,通过@RequestBody Integer[] ids接收前端传递的 ID 数组:

复制代码
@PostMapping("/delMore")
@ResponseBody
public Result delMore(@RequestBody Integer[] ids){
    System.out.println("ids="+ids);
    userService.deleteMore(ids);
    return new Result().success();
}

4.3 开启表格多选

table 的多选事件 @selection-change="handleSelectionChange"

4.4 添加批量删除按钮

复制代码
 <el-button type="danger" @click="deleteBatch">批量删除</el-button>

4.5 交互逻辑

定义批量删除数组,选中行的id集合传给后端

复制代码
const handleSelectionChange = (rows) => {  // rows 就是实际选择的数组
  data.rows = rows
}

const deleteMore = () => { 
  if (data.rows.length === 0) {
    ElMessage.warning('请选择要删除的记录')
    return
  }
    // 2. 提取纯ID数组(关键:只取id,不要整行数据)
  const ids = data.rows.map(item => item.id);
  ElMessageBox.confirm('删除后无法恢复,您确认删除吗?', '删除确认', { type: 'warning' }).then(res => {
    request.post('/user/delMore', ids).then(res => {
      if (res.code === '200') {
        ElMessage.success('删除成功')
        loadData()
      } else {
        ElMessage.error(res.msg)
      }
    })
  })
}

五、前端全部代码

复制代码
<template>
  <div>
    <div class="card" style="margin-bottom: 5px">
      <el-input clearable @clear="load" style="width: 260px; margin-right: 5px" v-model="data.username" placeholder="请输入用户名查询" :prefix-icon="Search"></el-input>
      <el-input clearable @clear="load" style="width: 260px; margin-right: 5px" v-model="data.address" placeholder="请输入地址查询" :prefix-icon="Search"></el-input>
      <el-button type="primary" @click="load">查 询</el-button>
      <el-button @click="reset">重 置</el-button>
    </div>
    <div class="card" style="margin-bottom: 5px">
      <el-button type="primary" @click="handleAdd">新 增</el-button>
      <el-button type="danger"@click="deleteBatch" >批量删除</el-button>
    </div>

   <div class="card" style="margin-bottom: 5px">
      <el-table :data="data.tableData" style="width: 100%" 
       @selection-change="handleSelectionChange"  :header-cell-style="{ color: '#333', backgroundColor: '#eaf4ff' }">
        <!-- type="selection":这指定该列将包含用于行选择的复选框。它允许用户在表格中选择一行或多行。 -->
        <el-table-column type="selection" width="55" />
        <el-table-column prop="username" label="用户名" width="180" />
        <el-table-column prop="birthday" label="生日" width="180"  :formatter="dateFormat" />
        <el-table-column prop="sex" label="性别" width="80" />
        <el-table-column prop="address" label="地址" width="180" />
        <el-table-column label="操作" width="100">
            <template #default="scope">
              <el-button type="primary" icon="Edit" circle @click="handleEdit(scope.row)"></el-button>
              <el-button type="danger" icon="Delete" circle @click="del(scope.row.id)"></el-button>
            </template>
        </el-table-column>
      </el-table>
    </div>
    <div class="card">
      <el-pagination
          v-model:current-page="data.pageNum"
          :page-size="data.pageSize"
          layout="total, prev, pager, next"
          :total="data.total"
          @current-change="load"
          @size-change="load"
      />
    </div>
  </div>

  <el-dialog title="信息" v-model="data.formVisible" width="30%" destroy-on-close>
      <el-form ref="formRef" :model="data.form" :rules="data.rules" label-width="80px" style="padding: 20px 30px 10px 0">
        <el-form-item prop="username" label="账号">
          <el-input v-model="data.form.username" autocomplete="off" />
        </el-form-item>
        <el-form-item prop="birthday" label="生日">
         <el-date-picker
                v-model="data.form.birthday"
                type="date"
                placeholder="选择日期时间"
          ></el-date-picker>
        </el-form-item>
        <el-form-item prop="sex" label="性别">
          <el-select v-model="data.form.sex" placeholder="请选择性别" style="width: 100%">
            <el-option label="男" value="男"></el-option>
            <el-option label="女" value="女"></el-option>
          </el-select>
        </el-form-item>
        <el-form-item prop="address" label="地址">
          <el-input v-model="data.form.address" autocomplete="off" />
        </el-form-item>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="data.formVisible = false">取 消</el-button>
          <el-button type="primary" @click="save">保 存</el-button>
        </div>
      </template>
</el-dialog>

</template>

<script setup>
import { reactive,ref } from "vue";
import {Search} from "@element-plus/icons-vue";
import request from "@/utils/request.js";
import {ElMessage, ElMessageBox } from "element-plus";

const formRef = ref() //表单实例

const data = reactive({
  name: null,
  pageNum: 1,
  pageSize: 5,
  total:0,
  tableData: [],
  rules: {
    username: [
      { required: true, message: '请填写用户名', trigger: 'blur' }
    ],
    birthday: [
      { required: true, message: '请填写生日', trigger: 'blur' }
    ],
    sex: [
      { required: true, message: '请填写性别', trigger: 'blur' }
    ],
    address: [
      { required: true, message: '请填写地址', trigger: 'blur' }
    ]
  },
  rows: []
})

const load = () => {
  request.get('/user/findAll', {
    params: {
      pageNum: data.pageNum,
      pageSize: data.pageSize,
      username: data.username,
      address: data.address
    }
  }).then(res => {
    if (res.code === '200') {
      data.tableData = res.data.list
      data.total = res.data.total
    } else {
      ElMessage.error(res.msg)
    }
  })
}
load()

//搜索重置
const reset = () => {
  data.username = null
  data.address = null
  load()
}

// 添加日期格式化函数
const dateFormat = (row, column, cellValue) => {
  if (!cellValue) return ''
  const date = new Date(cellValue)
  const year = date.getFullYear()
  const month = String(date.getMonth() + 1).padStart(2, '0')
  const day = String(date.getDate()).padStart(2, '0')
  return `${year}-${month}-${day}`
}

//点击新增按钮触发的方法
const handleAdd = () => {
  data.formVisible = true //显示弹框
  data.form = {}  //清空表单
}

const add = () => {
  console.log(data.form);
  
  // formRef 是表单的引用
  formRef.value.validate((valid) => {
    if (valid) {   // 验证通过的情况下
      request.post('/user/add', data.form).then(res => {
        if (res.code === '200') {
          data.formVisible = false //隐藏弹框
          ElMessage.success('新增成功')
          load() //重新加载数据
        } else {
          ElMessage.error(res.msg)
        }
      })
    }
  })
}


 //打开修改的弹框
const handleEdit = (row)=>{
  data.form = JSON.parse(JSON.stringify(row))  // 深度拷贝数据
  data.formVisible = true  
}

// //点击保存触发的方法
const save = () => {
  data.form.id ? update() : add()
}
//修改
const update = () => {
  // formRef 是表单的引用
  formRef.value.validate((valid) => {
    if (valid) {   // 验证通过的情况下
      request.post('/user/update', data.form).then(res => {
        if (res.code === '200') {
          data.formVisible = false
          ElMessage.success('修改成功')
          load()
        } else {
          ElMessage.error(res.msg)
        }
      })
    }
  })
}

//删除数据
const del = (id) => {
  ElMessageBox.confirm('删除后无法恢复,您确认删除吗?', '删除确认', { type: 'warning' }).then(res => {
    request.post('/user/delete/' + id).then(res => {
      if (res.code === '200') {
        ElMessage.success('删除成功')
        load()
      } else {
        ElMessage.error(res.msg)
      }
    })
  }).catch(err => {})
}

const handleSelectionChange = (rows) => {  // rows 就是实际选择的数组
  data.rows = rows
}

const deleteBatch = () => {
  if (data.rows.length === 0) {
    ElMessage.warning('请选择数据')
    return
  }
  ElMessageBox.confirm('删除后无法恢复,您确认删除吗?', '删除确认', { type: 'warning' }).then(res => {
    request.delete('/user/deleteBatch', { data: data.rows }).then(res => {
      if (res.code === '200') {
        ElMessage.success('批量删除成功')
        load()
      } else {
        ElMessage.error(res.msg)
      }
    })
  }).catch(err => {})
}
</script>
相关推荐
小二·2 小时前
Python Web 开发进阶实战:可持续计算 —— 在 Flask + Vue 中构建碳感知应用(Carbon-Aware Computing)
前端·python·flask
恒拓高科WorkPlus2 小时前
如何通过即时通讯工具提升团队协作?
前端·安全
钟佩颖2 小时前
Vue....
前端·javascript·vue.js
漂流瓶jz2 小时前
Polyfill方式解决前端兼容性问题:core-js包结构与各种配置策略
前端·javascript·webpack·ecmascript·babel·polyfill·core-js
Y淑滢潇潇2 小时前
WEB 模拟学校官网
前端·css
一只小bit2 小时前
Qt 网络:包含Udp、Tcp、Http三种协议的客户端实践手册
前端·c++·qt·页面
We་ct2 小时前
LeetCode 238. 除了自身以外数组的乘积|最优解详解(O(n)时间+O(1)空间)
前端·算法·leetcode·typescript
AC赳赳老秦2 小时前
低代码开发中的高效调试:基于 DeepSeek 的报错日志解析与自动修复方案生成
前端·javascript·低代码·postgresql·数据库架构·easyui·deepseek
迪霸戈2 小时前
MyBatis动态SQL避坑:为什么List用`[0]`而不是`get(0)`
sql·list·mybatis