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

332 lines
9.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 车辆删除功能最终修复 - 使用 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` 注解:
```java
@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 子句之外**
### 错误的实现方式
```java
// ❌ 错误:使用 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()`
```java
/**
* 删除车辆(逻辑删除,使用 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() 后**
```sql
UPDATE vehicle
SET is_delete = 1
WHERE id = 3;
```
**完美!** 只更新 `is_delete` 字段,不涉及其他字段。
## MyBatis-Plus @TableLogic 工作原理
### 1. SELECT 操作
```java
Vehicle vehicle = vehicleMapper.selectById(1);
```
**生成的 SQL**
```sql
SELECT * FROM vehicle WHERE id = 1 AND is_delete = 0
```
### 2. DELETE 操作(逻辑删除)
```java
vehicleMapper.deleteById(1);
```
**生成的 SQL**
```sql
UPDATE vehicle SET is_delete = 1 WHERE id = 1
```
### 3. UPDATE 操作(注意!)
```java
vehicle.setIsDelete(1);
vehicleMapper.updateById(vehicle);
```
**生成的 SQL**
```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
```java
vehicle.setIsDelete(1);
vehicleMapper.updateById(vehicle);
```
**失败原因**`@TableLogic` 会排除 `is_delete` 字段
### 尝试2使用 LambdaUpdateWrapper
```java
LambdaUpdateWrapper<Vehicle> wrapper = new LambdaUpdateWrapper<>();
wrapper.set(Vehicle::getIsDelete, 1);
vehicleMapper.update(null, wrapper);
```
**问题**:代码复杂,而且可能与 `@TableLogic` 冲突
### 最终方案:直接使用 deleteById
```java
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. 验证数据库
```sql
-- 查看该记录
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
```
```sql
-- 前端列表查询(不应包含该记录)
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 = 1``selectById` 自动过滤了它
**解决**: 正常现象,说明删除成功。前端应该已经刷新列表。
### Q4: 如何查看所有已删除的记录?
**SQL**:
```sql
SELECT * FROM vehicle WHERE is_delete = 1;
```
### Q5: 如何恢复已删除的记录?
**SQL**:
```sql
UPDATE vehicle
SET is_delete = 0,
update_time = NOW()
WHERE id = 3;
```
## MyBatis-Plus @TableLogic 最佳实践
### ✅ 推荐做法
1. **删除操作**:使用 `deleteById()``deleteBatchIds()`
```java
vehicleMapper.deleteById(id); // 逻辑删除
```
2. **查询操作**:正常使用,自动过滤已删除记录
```java
vehicleMapper.selectById(id); // 自动添加 is_delete=0
```
3. **更新操作**:不要尝试修改 `is_delete` 字段
```java
vehicle.setLicensePlate("新车牌");
vehicleMapper.updateById(vehicle); // is_delete 不会被更新
```
### ❌ 避免的做法
1. **不要用 updateById 修改 is_delete**
```java
// ❌ 错误:不会生效
vehicle.setIsDelete(1);
vehicleMapper.updateById(vehicle);
```
2. **不要在 WHERE 条件中手动添加 is_delete**
```java
// ❌ 多余:@TableLogic 会自动添加
queryWrapper.eq(Vehicle::getIsDelete, 0);
```
3. **不要尝试物理删除**
```java
// ❌ 这仍然是逻辑删除,不是物理删除
vehicleMapper.deleteById(id);
```
## 总结
✅ **最终修复方案**
- 使用 `vehicleMapper.deleteById(id)` 执行逻辑删除
- 让 MyBatis-Plus 的 `@TableLogic` 自动处理 `is_delete` 字段
- 代码简洁,性能优秀,符合框架设计理念
✅ **核心原理**
- `@TableLogic` 会在 DELETE 操作时自动转换为 UPDATE
- 不要用 UPDATE 操作来修改逻辑删除标记
- 利用框架特性,而不是绕过它
✅ **用户体验**
- 删除成功后列表自动刷新
- 已删除的记录立即消失
- 操作简单,性能优秀
🎯 **关键教训**
当框架提供了特定功能(如 `@TableLogic`)时,应该按照框架的设计意图使用,而不是尝试绕过或覆盖它。`deleteById()` 是正确的删除方式,`updateById()` 不适合修改逻辑删除标记。