332 lines
9.3 KiB
Markdown
332 lines
9.3 KiB
Markdown
# 车辆删除功能最终修复 - 使用 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()` 不适合修改逻辑删除标记。
|
||
|