Skip to content

Commit 2fc7a8e

Browse files
yyyCodeclaude
andcommitted
perf: fix N+1 queries with batch author/category loading
Replace per-article userMapper.selectById() and categoryService.resolveMeta() calls in bulk paths with a new mapListItems() that preloads all authors via selectBatchIds() and caches category metas per unique ID. Affected callers: - listPublished() — public article list - listMine() — user's own article list - ArticleSearchService.searchByEs() — ES search results - ArticleSearchService.searchByMysql() — MySQL fallback search Before: 20 articles = 1 + 20 author queries + 20 category queries = 41 DB calls After: 20 articles = 1 + 1 author query + N unique categories = 2–5 DB calls Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 3e3d7dd commit 2fc7a8e

4 files changed

Lines changed: 58 additions & 11 deletions

File tree

.superpowers/brainstorm/1761-1782143253/state/server-info

Lines changed: 0 additions & 1 deletion
This file was deleted.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"reason":"idle timeout","timestamp":1782150575527}

OpenBlog-business/src/main/java/com/yqz/openblog/article/service/ArticleService.java

Lines changed: 53 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,8 @@
2323
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
2424

2525
import java.time.Instant;
26-
import java.util.List;
27-
import java.util.Optional;
28-
import java.util.Set;
26+
import java.util.*;
27+
import java.util.stream.Collectors;
2928

3029
import org.springframework.beans.factory.annotation.Autowired;
3130
import org.springframework.stereotype.Service;
@@ -86,6 +85,55 @@ public ArticleListItemResponse mapListItem(Article a) {
8685
return resp;
8786
}
8887

88+
/**
89+
* 批量映射文章列表 — 预加载作者和分类信息,避免 N+1 查询。
90+
* <p>
91+
* 原 {@link #mapListItem(Article)} 每篇文章单独查作者和分类(N+1),
92+
* 此方法改为:1 次批量查所有作者 + 按需查分类(同一分类只查一次)。
93+
* 单篇文章场景仍可使用 mapListItem。
94+
*/
95+
public List<ArticleListItemResponse> mapListItems(List<Article> articles) {
96+
if (articles.isEmpty()) return Collections.emptyList();
97+
98+
// 批量加载所有作者:1 次 DB 查询
99+
Set<Long> authorIds = articles.stream()
100+
.map(Article::getAuthorId)
101+
.filter(Objects::nonNull)
102+
.collect(Collectors.toSet());
103+
final Map<Long, User> userMap = authorIds.isEmpty()
104+
? Collections.emptyMap()
105+
: userMapper.selectBatchIds(authorIds).stream()
106+
.collect(Collectors.toMap(User::getId, u -> u, (a, b) -> a));
107+
108+
// 按 categoryId 缓存 resolveMeta 结果(resolveMeta 内部查全表,复用即可)
109+
Map<Long, CategoryService.CategoryMeta> catCache = new HashMap<>();
110+
111+
return articles.stream().map(a -> {
112+
ArticleListItemResponse resp = new ArticleListItemResponse();
113+
resp.setId(a.getId());
114+
resp.setTitle(a.getTitle());
115+
resp.setSummary(a.getSummary());
116+
resp.setCoverMediaKey(a.getCoverMediaKey());
117+
resp.setAuthorId(a.getAuthorId());
118+
User author = userMap.get(a.getAuthorId());
119+
resp.setAuthorNickname(author != null ? author.getUsername() : null);
120+
resp.setPublishedAt(a.getPublishedAt());
121+
resp.setStatus(a.getStatus());
122+
resp.setLikeCount(a.getLikeCount());
123+
resp.setViewCount(a.getViewCount() == null ? 0L : a.getViewCount());
124+
resp.setFavoriteCount(a.getFavoriteCount());
125+
resp.setCommentCount(a.getCommentCount());
126+
127+
CategoryService.CategoryMeta meta = catCache.computeIfAbsent(
128+
a.getCategoryId(), categoryService::resolveMeta);
129+
resp.setCategoryId(meta.getCategoryId());
130+
resp.setCategoryName(meta.getCategoryName());
131+
resp.setCategoryPath(meta.getCategoryPath());
132+
133+
return resp;
134+
}).collect(Collectors.toList());
135+
}
136+
89137
public ArticleDetailResponse mapDetail(Article a) {
90138
ArticleDetailResponse resp = new ArticleDetailResponse();
91139
resp.setId(a.getId());
@@ -187,7 +235,7 @@ public PageResult<ArticleListItemResponse> listPublished(int page, int size, Lon
187235
w.in(Article::getCategoryId, ids);
188236
}
189237
IPage<Article> p = articleMapper.selectPage(mpPage, w);
190-
List<ArticleListItemResponse> items = p.getRecords().stream().map(this::mapListItem).toList();
238+
List<ArticleListItemResponse> items = mapListItems(p.getRecords());
191239
PageResult<ArticleListItemResponse> result = new PageResult<>(items, page, size, p.getTotal());
192240
publishedContentCache.putList(categoryId, page, size, result);
193241
return result;
@@ -396,7 +444,7 @@ public PageResult<ArticleListItemResponse> listMine(Long authorId, int page, int
396444
.in(Article::getStatus, statuses)
397445
.orderByDesc(Article::getCreatedAt);
398446
IPage<Article> p = articleMapper.selectPage(mpPage, w);
399-
List<ArticleListItemResponse> items = p.getRecords().stream().map(this::mapListItem).toList();
447+
List<ArticleListItemResponse> items = mapListItems(p.getRecords());
400448
return new PageResult<>(items, page, size, p.getTotal());
401449
}
402450

OpenBlog-business/src/main/java/com/yqz/openblog/search/service/ArticleSearchService.java

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -74,18 +74,19 @@ public PageResult<ArticleListItemResponse> search(String keyword, int page, int
7474
private PageResult<ArticleListItemResponse> searchByEs(String keyword, int page, int size) {
7575
SearchResult result = searchOps.search(INDEX_NAME, keyword, SEARCH_FIELDS, page, size);
7676

77-
List<ArticleListItemResponse> items = new ArrayList<>();
77+
List<Article> articles = new ArrayList<>();
7878
for (Map<String, Object> hit : result.getHits()) {
7979
Object idObj = hit.get("id");
8080
if (idObj != null) {
8181
Long articleId = Long.valueOf(idObj.toString());
8282
Article article = articleMapper.selectById(articleId);
8383
if (article != null && article.getStatus() == ArticleStatus.PUBLISHED) {
84-
items.add(articleService.mapListItem(article));
84+
articles.add(article);
8585
}
8686
}
8787
}
8888

89+
List<ArticleListItemResponse> items = articleService.mapListItems(articles);
8990
return new PageResult<>(items, page, size, result.getTotalHits());
9091
}
9192

@@ -127,9 +128,7 @@ private PageResult<ArticleListItemResponse> searchByMysql(String keyword, int pa
127128
}
128129
}
129130

130-
List<ArticleListItemResponse> items = articlePage.getRecords().stream()
131-
.map(articleService::mapListItem)
132-
.collect(Collectors.toList());
131+
List<ArticleListItemResponse> items = articleService.mapListItems(articlePage.getRecords());
133132

134133
return new PageResult<>(items, page, size, articlePage.getTotal());
135134
}

0 commit comments

Comments
 (0)