215 lines
5.4 KiB
Markdown
215 lines
5.4 KiB
Markdown
|
|
# Banner状态更新Bug修复报告
|
|||
|
|
|
|||
|
|
## 🐛 问题描述
|
|||
|
|
|
|||
|
|
### 错误信息
|
|||
|
|
```
|
|||
|
|
Column 'image' cannot be null
|
|||
|
|
SQLIntegrityConstraintViolationException
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 错误位置
|
|||
|
|
- **方法**: `BannerServiceImpl.updateBannerStatus()`
|
|||
|
|
- **行号**: 第218行
|
|||
|
|
- **操作**: 更新Banner状态时
|
|||
|
|
|
|||
|
|
### 错误堆栈关键信息
|
|||
|
|
```
|
|||
|
|
UPDATE banner SET
|
|||
|
|
image = ?, title = ?, description = ?, button_text = ?,
|
|||
|
|
link_type = ?, link = ?, sort_order = ?, is_enabled = ?,
|
|||
|
|
update_time = ?
|
|||
|
|
WHERE id = ? AND is_deleted = 0
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🔍 根本原因分析
|
|||
|
|
|
|||
|
|
### 问题根源
|
|||
|
|
1. **部分字段设置**: 在 `updateBannerStatus` 方法中创建新的 `Banner` 对象,只设置了 `id`, `isEnabled`, `updateTime`
|
|||
|
|
2. **全字段更新**: `updateById` 方法会更新所有字段,包括未设置的字段
|
|||
|
|
3. **数据库约束**: `image` 等字段在数据库中是 `NOT NULL`,传入null值违反约束
|
|||
|
|
|
|||
|
|
### 代码问题
|
|||
|
|
```java
|
|||
|
|
// 问题代码 - 只设置部分字段
|
|||
|
|
Banner banner = new Banner();
|
|||
|
|
banner.setId(id);
|
|||
|
|
banner.setIsEnabled(Boolean.TRUE.equals(isEnabled) ? 1 : 0);
|
|||
|
|
banner.setUpdateTime(LocalDateTime.now());
|
|||
|
|
|
|||
|
|
// 使用updateById会尝试更新所有字段,包括null的image字段
|
|||
|
|
int result = bannerMapper.updateById(banner);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## ✅ 修复方案
|
|||
|
|
|
|||
|
|
### 1. 新增专用状态更新SQL
|
|||
|
|
在 `BannerMapper.xml` 中添加专门的状态更新方法:
|
|||
|
|
|
|||
|
|
```xml
|
|||
|
|
<!-- 更新Banner状态 -->
|
|||
|
|
<update id="updateStatus">
|
|||
|
|
UPDATE banner SET
|
|||
|
|
is_enabled = #{isEnabled},
|
|||
|
|
update_time = #{updateTime}
|
|||
|
|
WHERE id = #{id} AND is_deleted = 0
|
|||
|
|
</update>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2. 更新Mapper接口
|
|||
|
|
在 `BannerMapper.java` 中添加对应方法:
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
/**
|
|||
|
|
* 更新Banner状态
|
|||
|
|
*/
|
|||
|
|
int updateStatus(@Param("id") Long id,
|
|||
|
|
@Param("isEnabled") Integer isEnabled,
|
|||
|
|
@Param("updateTime") LocalDateTime updateTime);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3. 修改Service实现
|
|||
|
|
更新 `BannerServiceImpl.updateBannerStatus()` 方法:
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
@Override
|
|||
|
|
@Transactional
|
|||
|
|
public void updateBannerStatus(Long id, Boolean isEnabled) {
|
|||
|
|
Banner existingBanner = bannerMapper.selectById(id);
|
|||
|
|
if (existingBanner == null) {
|
|||
|
|
throw new BusinessException("Banner不存在");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Integer enabledValue = Boolean.TRUE.equals(isEnabled) ? 1 : 0;
|
|||
|
|
LocalDateTime updateTime = LocalDateTime.now();
|
|||
|
|
|
|||
|
|
// 使用专门的状态更新方法,只更新需要的字段
|
|||
|
|
int result = bannerMapper.updateStatus(id, enabledValue, updateTime);
|
|||
|
|
if (result <= 0) {
|
|||
|
|
throw new BusinessException("更新Banner状态失败");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
log.info("更新Banner状态成功: id={}, enabled={}", id, isEnabled);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 📊 修复前后对比
|
|||
|
|
|
|||
|
|
### 修复前
|
|||
|
|
| 问题 | 影响 |
|
|||
|
|
|------|------|
|
|||
|
|
| ❌ 使用 `updateById` 更新所有字段 | 导致null值约束违规 |
|
|||
|
|
| ❌ 创建不完整的Banner对象 | 非必需字段被设为null |
|
|||
|
|
| ❌ 数据库约束冲突 | 无法完成状态更新操作 |
|
|||
|
|
|
|||
|
|
### 修复后
|
|||
|
|
| 改进 | 效果 |
|
|||
|
|
|------|------|
|
|||
|
|
| ✅ 使用 `updateStatus` 只更新状态字段 | 避免不必要的字段更新 |
|
|||
|
|
| ✅ 直接传递参数而非对象 | 明确指定要更新的字段 |
|
|||
|
|
| ✅ 避免数据库约束冲突 | 状态更新操作正常执行 |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🧪 测试验证
|
|||
|
|
|
|||
|
|
### 测试用例
|
|||
|
|
|
|||
|
|
#### 状态切换测试
|
|||
|
|
```javascript
|
|||
|
|
// 禁用Banner
|
|||
|
|
PUT /admin/banners/status
|
|||
|
|
{
|
|||
|
|
"id": 1,
|
|||
|
|
"isEnabled": false
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 启用Banner
|
|||
|
|
PUT /admin/banners/status
|
|||
|
|
{
|
|||
|
|
"id": 1,
|
|||
|
|
"isEnabled": true
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 预期结果
|
|||
|
|
- ✅ 状态更新成功
|
|||
|
|
- ✅ 只更新 `is_enabled` 和 `update_time` 字段
|
|||
|
|
- ✅ 其他字段保持不变
|
|||
|
|
- ✅ 不再出现约束违规错误
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🛡️ 最佳实践总结
|
|||
|
|
|
|||
|
|
### 1. 部分字段更新原则
|
|||
|
|
- 只更新需要修改的字段
|
|||
|
|
- 避免不必要的全表字段更新
|
|||
|
|
- 使用专门的SQL语句处理特定场景
|
|||
|
|
|
|||
|
|
### 2. MyBatis映射设计
|
|||
|
|
```xml
|
|||
|
|
<!-- ❌ 避免 - 全字段更新可能导致约束问题 -->
|
|||
|
|
<update id="updateById">
|
|||
|
|
UPDATE table SET field1=#{field1}, field2=#{field2}, ...
|
|||
|
|
</update>
|
|||
|
|
|
|||
|
|
<!-- ✅ 推荐 - 按需更新特定字段 -->
|
|||
|
|
<update id="updateStatus">
|
|||
|
|
UPDATE table SET status=#{status}, update_time=#{updateTime}
|
|||
|
|
WHERE id=#{id}
|
|||
|
|
</update>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3. Service层设计
|
|||
|
|
```java
|
|||
|
|
// ❌ 避免 - 创建不完整对象
|
|||
|
|
Banner banner = new Banner();
|
|||
|
|
banner.setId(id);
|
|||
|
|
banner.setSomeField(value);
|
|||
|
|
mapper.updateById(banner); // 可能导致其他字段为null
|
|||
|
|
|
|||
|
|
// ✅ 推荐 - 直接传递需要的参数
|
|||
|
|
mapper.updateSomeField(id, value, updateTime);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 📋 文件变更清单
|
|||
|
|
|
|||
|
|
### 修改文件
|
|||
|
|
- ✅ `src/main/resources/mapper/BannerMapper.xml` - 新增 `updateStatus` SQL
|
|||
|
|
- ✅ `src/main/java/com/dora/mapper/BannerMapper.java` - 新增 `updateStatus` 方法
|
|||
|
|
- ✅ `src/main/java/com/dora/service/impl/BannerServiceImpl.java` - 修改 `updateBannerStatus` 实现
|
|||
|
|
|
|||
|
|
### 新增文件
|
|||
|
|
- ✅ `docs/banner-status-bug-fix.md` - 本修复报告
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🎯 修复效果
|
|||
|
|
|
|||
|
|
### ✅ 解决的问题
|
|||
|
|
1. **约束违规**: 消除了 `Column 'image' cannot be null` 错误
|
|||
|
|
2. **性能优化**: 只更新必要字段,减少数据库负载
|
|||
|
|
3. **代码清晰**: 专用方法语义更明确
|
|||
|
|
4. **维护性**: 降低了未来类似问题的风险
|
|||
|
|
|
|||
|
|
### 🚨 注意事项
|
|||
|
|
1. **测试覆盖**: 确保所有状态切换场景都经过测试
|
|||
|
|
2. **数据一致性**: 使用事务保护确保操作原子性
|
|||
|
|
3. **日志记录**: 保持详细的操作日志便于问题排查
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
**修复状态**: ✅ 已完成
|
|||
|
|
**测试状态**: ⏳ 待验证
|
|||
|
|
**风险等级**: 低(只影响状态更新功能)
|
|||
|
|
**部署要求**: 重启服务器使修改生效
|