feat: Java

开发规范新增 N+1 查询防范章节

  - 新增 N+1 查询防范规则,禁止循环内调用 Repository/Mapper
  - 提供 count/findById/exists 三种场景的修复模式
  - 性能优化表格 N+1 行指向新章节,避免重复
This commit is contained in:
wwj committed 2026-02-27 16:54:13 +08:00
1 parent 44a133d104
commit 6967f6c8ba
1 file changed
+33 -1
+33 -1
View File
@@ -130,6 +130,38 @@ List<User> users = batchQuery(allIds, 500, ids -> userRepository.findByIdIn(ids)
---
## N+1 查询防范
| 规则 | 说明 |
|------|------|
| ❌ 禁止循环内调用 Repository/Mapper | stream/forEach/for 内每次迭代触发一次查询 |
| ✅ 循环外批量查询,结果转 Map | 查询次数从 N 降为 1(或 distinct 数) |
```java
// ❌ N+1:循环内逐行查询 count
records.forEach(record -> {
long count = deviceRepo.countByDeviceId(record.getDeviceId()); // 每条触发一次查询
record.setDeviceCount(count);
});
// ✅ 循环外批量查询 + Map 查找
List<String> deviceIds = records.stream()
.map(Record::getDeviceId).distinct().collect(Collectors.toList());
Map<String, Long> countMap = deviceRepo.countByDeviceIdIn(deviceIds).stream()
.collect(Collectors.toMap(CountDTO::getDeviceId, CountDTO::getCount));
records.forEach(r -> r.setDeviceCount(countMap.getOrDefault(r.getDeviceId(), 0L)));
```
常见 N+1 场景及修复模式:
| 场景 | 循环内(❌) | 循环外(✅) |
|------|------------|------------|
| count | `repo.countByXxx(id)` | `repo.countByXxxIn(ids)` → `Map<id, count>` |
| findById | `repo.findById(id)` | `repo.findByIdIn(ids)` → `Map<id, entity>` |
| exists | `repo.existsByXxx(id)` | `repo.findXxxIn(ids)` → `Set<id>` + `set.contains()` |
---
## 异常处理
```java
@@ -242,7 +274,7 @@ public class UserController {
| 陷阱 | 解决方案 |
|------|---------|
| N+1 查询 | 使用 JOIN FETCH 或批量查询 |
| N+1 查询 | 见「N+1 查询防范」章节 |
| 循环拼接字符串 | 使用 `StringBuilder` |
| 频繁装箱拆箱 | 使用原始类型流 |
| 未指定集合初始容量 | `new ArrayList<>(size)` |