Files
cattleTransportation/VEHICLE_DELETE_FINAL_FIX.md
2025-10-29 17:33:32 +08:00

9.3 KiB
Raw Blame History

车辆删除功能最终修复 - 使用 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 的行为

  1. SELECT: 自动添加 WHERE is_delete = 0
  2. DELETE: 自动转换为 UPDATE SET is_delete = 1
  3. 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. 测试删除功能

  1. 进入"车辆管理"页面
  2. 选择要删除的车辆,例如:鄂A 66662
  3. 点击"删除"按钮
  4. 确认删除

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: 删除后前端列表还显示该记录?

检查:

  1. 后端服务是否重启
  2. 浏览器缓存是否清除Ctrl+F5
  3. 后端日志中 SQL 是否为 UPDATE SET is_delete=1
  4. 数据库中该记录的 is_delete 是否为 1

Q2: 后端日志显示 SQL 中没有 is_delete 字段?

原因: 代码没有更新,还在使用 updateById()

解决:

  1. 确认代码已修改为 deleteById()
  2. 重新编译:mvn clean compile
  3. 重启后端服务

Q3: 删除后报错 "车辆不存在"

原因: 记录已经被删除(is_delete = 1selectById 自动过滤了它

解决: 正常现象,说明删除成功。前端应该已经刷新列表。

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 最佳实践

推荐做法

  1. 删除操作:使用 deleteById()deleteBatchIds()

    vehicleMapper.deleteById(id);  // 逻辑删除
    
  2. 查询操作:正常使用,自动过滤已删除记录

    vehicleMapper.selectById(id);  // 自动添加 is_delete=0
    
  3. 更新操作:不要尝试修改 is_delete 字段

    vehicle.setLicensePlate("新车牌");
    vehicleMapper.updateById(vehicle);  // is_delete 不会被更新
    

避免的做法

  1. 不要用 updateById 修改 is_delete

    // ❌ 错误:不会生效
    vehicle.setIsDelete(1);
    vehicleMapper.updateById(vehicle);
    
  2. 不要在 WHERE 条件中手动添加 is_delete

    // ❌ 多余:@TableLogic 会自动添加
    queryWrapper.eq(Vehicle::getIsDelete, 0);
    
  3. 不要尝试物理删除

    // ❌ 这仍然是逻辑删除,不是物理删除
    vehicleMapper.deleteById(id);
    

总结

最终修复方案

  • 使用 vehicleMapper.deleteById(id) 执行逻辑删除
  • 让 MyBatis-Plus 的 @TableLogic 自动处理 is_delete 字段
  • 代码简洁,性能优秀,符合框架设计理念

核心原理

  • @TableLogic 会在 DELETE 操作时自动转换为 UPDATE
  • 不要用 UPDATE 操作来修改逻辑删除标记
  • 利用框架特性,而不是绕过它

用户体验

  • 删除成功后列表自动刷新
  • 已删除的记录立即消失
  • 操作简单,性能优秀

🎯 关键教训 当框架提供了特定功能(如 @TableLogic)时,应该按照框架的设计意图使用,而不是尝试绕过或覆盖它。deleteById() 是正确的删除方式,updateById() 不适合修改逻辑删除标记。