9.3 KiB
9.3 KiB
车辆删除功能最终修复 - 使用 MyBatis-Plus 的 @TableLogic
问题描述
用户反馈:删除车辆返回成功,但前端页面依然显示该记录。
后端日志显示:
UPDATE vehicle SET license_plate=?, ..., update_time=?, updated_by=?
WHERE id=? AND is_delete=0
Updates: 1
关键发现:UPDATE 语句中没有 is_delete 字段!
问题根源
@TableLogic 注解的副作用
Vehicle 实体类使用了 @TableLogic 注解:
@TableLogic(value = "0", delval = "1")
@TableField("is_delete")
private Integer isDelete;
@TableLogic 的行为:
- ✅ SELECT: 自动添加
WHERE is_delete = 0 - ✅ DELETE: 自动转换为
UPDATE SET is_delete = 1 - ❌ UPDATE:
is_delete字段被排除在 SET 子句之外!
错误的实现方式
// ❌ 错误:使用 updateById 无法修改 is_delete 字段
Vehicle vehicle = vehicleMapper.selectById(id);
vehicle.setIsDelete(1); // 这个设置会被忽略!
vehicleMapper.updateById(vehicle);
// 生成的 SQL(注意没有 is_delete):
UPDATE vehicle
SET license_plate=?, car_front_photo=?, ..., update_time=?, updated_by=?
WHERE id=? AND is_delete=0
结果:
is_delete字段没有被更新,仍然是0- 记录依然存在,前端列表继续显示
- 虽然返回
Updates: 1(更新了其他字段),但逻辑删除失败
最终解决方案
使用 MyBatis-Plus 的 deleteById() 方法
正确做法:利用 @TableLogic 注解,直接调用 deleteById()
/**
* 删除车辆(逻辑删除,使用 MyBatis-Plus 的 deleteById 自动处理)
* 因为实体类使用了 @TableLogic 注解,deleteById 会自动将 is_delete 设置为 1
*/
@Override
@Transactional
public AjaxResult deleteVehicle(Integer id) {
System.out.println("[VEHICLE-DELETE] 开始逻辑删除车辆,ID: " + id);
if (id == null) {
System.out.println("[VEHICLE-DELETE] 删除失败:车辆ID为空");
return AjaxResult.error("车辆ID不能为空");
}
// 查询车辆是否存在(@TableLogic 会自动过滤 is_delete=1 的记录)
Vehicle vehicle = vehicleMapper.selectById(id);
if (vehicle == null) {
System.out.println("[VEHICLE-DELETE] 删除失败:车辆不存在或已被删除,ID: " + id);
return AjaxResult.error("车辆不存在");
}
System.out.println("[VEHICLE-DELETE] 车辆信息 - 车牌号: " + vehicle.getLicensePlate() + ", 当前 is_delete: " + vehicle.getIsDelete());
// 获取当前用户ID(用于日志记录)
Integer userId = SecurityUtil.getCurrentUserId();
// ✅ 使用 MyBatis-Plus 的 deleteById 方法
// 因为实体类有 @TableLogic 注解,这会自动执行逻辑删除(UPDATE SET is_delete=1)
// 而不是物理删除(DELETE FROM)
boolean success = vehicleMapper.deleteById(id) > 0;
if (success) {
System.out.println("[VEHICLE-DELETE] ✅ 逻辑删除成功,车牌号: " + vehicle.getLicensePlate() + ", 操作人ID: " + userId);
return AjaxResult.success("删除车辆成功");
} else {
System.out.println("[VEHICLE-DELETE] ❌ 逻辑删除失败,车牌号: " + vehicle.getLicensePlate());
return AjaxResult.error("删除车辆失败");
}
}
生成的 SQL
使用 deleteById() 后:
UPDATE vehicle
SET is_delete = 1
WHERE id = 3;
完美! 只更新 is_delete 字段,不涉及其他字段。
MyBatis-Plus @TableLogic 工作原理
1. SELECT 操作
Vehicle vehicle = vehicleMapper.selectById(1);
生成的 SQL:
SELECT * FROM vehicle WHERE id = 1 AND is_delete = 0
2. DELETE 操作(逻辑删除)
vehicleMapper.deleteById(1);
生成的 SQL:
UPDATE vehicle SET is_delete = 1 WHERE id = 1
3. UPDATE 操作(注意!)
vehicle.setIsDelete(1);
vehicleMapper.updateById(vehicle);
生成的 SQL:
-- ❌ is_delete 不在 SET 子句中!
UPDATE vehicle
SET license_plate=?, car_front_photo=?, ..., update_time=?
WHERE id=? AND is_delete=0
结论:
- ✅ 删除时用
deleteById(),它会自动设置is_delete = 1 - ❌ 不要用
updateById()来修改is_delete字段
修复前后对比
| 对比项 | 修复前(错误) | 修复后(正确) |
|---|---|---|
| 方法调用 | updateById(vehicle) |
deleteById(id) |
| 生成的 SQL | UPDATE SET 所有字段... |
UPDATE SET is_delete=1 |
| is_delete 是否更新 | ❌ 否(被排除) | ✅ 是 |
| 其他字段是否更新 | ✅ 是(不必要) | ❌ 否(只更新必要字段) |
| 性能 | 差(更新所有字段) | 好(只更新1个字段) |
| 逻辑删除是否生效 | ❌ 否 | ✅ 是 |
为什么之前的方案失败
尝试1:使用 updateById
vehicle.setIsDelete(1);
vehicleMapper.updateById(vehicle);
失败原因:@TableLogic 会排除 is_delete 字段
尝试2:使用 LambdaUpdateWrapper
LambdaUpdateWrapper<Vehicle> wrapper = new LambdaUpdateWrapper<>();
wrapper.set(Vehicle::getIsDelete, 1);
vehicleMapper.update(null, wrapper);
问题:代码复杂,而且可能与 @TableLogic 冲突
最终方案:直接使用 deleteById
vehicleMapper.deleteById(id);
优势:
- ✅ 代码简洁
- ✅ 利用 MyBatis-Plus 的特性
- ✅ 自动处理逻辑删除
- ✅ 与
@TableLogic完美配合
测试步骤
1. 重启后端服务
确保新的代码生效。
2. 清除浏览器缓存
强制刷新前端页面(Ctrl+F5)。
3. 测试删除功能
- 进入"车辆管理"页面
- 选择要删除的车辆,例如:
鄂A 66662 - 点击"删除"按钮
- 确认删除
4. 观察后端日志
修复后应该看到:
[VEHICLE-DELETE] 开始逻辑删除车辆,ID: 3
[VEHICLE-DELETE] 车辆信息 - 车牌号: 鄂A 66662, 当前 is_delete: 0
UPDATE vehicle SET is_delete=1 WHERE id=3
[VEHICLE-DELETE] ✅ 逻辑删除成功,车牌号: 鄂A 66662, 操作人ID: 11
关键点:
- ✅ SQL 中只有
SET is_delete=1 - ✅ 没有更新其他字段
- ✅ 日志显示"逻辑删除成功"
5. 验证前端列表
- ✅ 列表自动刷新
- ✅ 已删除的车辆不再显示
- ✅ 显示"删除成功"提示
6. 验证数据库
-- 查看该记录
SELECT id, license_plate, is_delete, update_time
FROM vehicle
WHERE id = 3;
-- 预期结果:
-- id | license_plate | is_delete | update_time
-- 3 | 鄂A 66662 | 1 | 2025-10-29 17:00:00
-- 前端列表查询(不应包含该记录)
SELECT * FROM vehicle WHERE is_delete = 0;
常见问题排查
Q1: 删除后前端列表还显示该记录?
检查:
- 后端服务是否重启
- 浏览器缓存是否清除(Ctrl+F5)
- 后端日志中 SQL 是否为
UPDATE SET is_delete=1 - 数据库中该记录的
is_delete是否为1
Q2: 后端日志显示 SQL 中没有 is_delete 字段?
原因: 代码没有更新,还在使用 updateById()
解决:
- 确认代码已修改为
deleteById() - 重新编译:
mvn clean compile - 重启后端服务
Q3: 删除后报错 "车辆不存在"?
原因: 记录已经被删除(is_delete = 1),selectById 自动过滤了它
解决: 正常现象,说明删除成功。前端应该已经刷新列表。
Q4: 如何查看所有已删除的记录?
SQL:
SELECT * FROM vehicle WHERE is_delete = 1;
Q5: 如何恢复已删除的记录?
SQL:
UPDATE vehicle
SET is_delete = 0,
update_time = NOW()
WHERE id = 3;
MyBatis-Plus @TableLogic 最佳实践
✅ 推荐做法
-
删除操作:使用
deleteById()或deleteBatchIds()vehicleMapper.deleteById(id); // 逻辑删除 -
查询操作:正常使用,自动过滤已删除记录
vehicleMapper.selectById(id); // 自动添加 is_delete=0 -
更新操作:不要尝试修改
is_delete字段vehicle.setLicensePlate("新车牌"); vehicleMapper.updateById(vehicle); // is_delete 不会被更新
❌ 避免的做法
-
不要用 updateById 修改 is_delete
// ❌ 错误:不会生效 vehicle.setIsDelete(1); vehicleMapper.updateById(vehicle); -
不要在 WHERE 条件中手动添加 is_delete
// ❌ 多余:@TableLogic 会自动添加 queryWrapper.eq(Vehicle::getIsDelete, 0); -
不要尝试物理删除
// ❌ 这仍然是逻辑删除,不是物理删除 vehicleMapper.deleteById(id);
总结
✅ 最终修复方案:
- 使用
vehicleMapper.deleteById(id)执行逻辑删除 - 让 MyBatis-Plus 的
@TableLogic自动处理is_delete字段 - 代码简洁,性能优秀,符合框架设计理念
✅ 核心原理:
@TableLogic会在 DELETE 操作时自动转换为 UPDATE- 不要用 UPDATE 操作来修改逻辑删除标记
- 利用框架特性,而不是绕过它
✅ 用户体验:
- 删除成功后列表自动刷新
- 已删除的记录立即消失
- 操作简单,性能优秀
🎯 关键教训:
当框架提供了特定功能(如 @TableLogic)时,应该按照框架的设计意图使用,而不是尝试绕过或覆盖它。deleteById() 是正确的删除方式,updateById() 不适合修改逻辑删除标记。