From 190fbe835fe6ab2b60c2edf2c9637abb321a0d04 Mon Sep 17 00:00:00 2001 From: ByungCheol Date: Thu, 2 Jul 2026 01:47:08 +0900 Subject: [PATCH] =?UTF-8?q?perf(account):=20=EB=82=B4=EC=97=AD=20=EB=AA=A9?= =?UTF-8?q?=EB=A1=9D=20=ED=83=9C=EA=B7=B8=20=EC=A1=B0=ED=9A=8C=20N+1=20?= =?UTF-8?q?=E2=86=92=20=EB=B0=B0=EC=B9=98=20=EB=8B=A8=EC=9D=BC=20=EC=BF=BC?= =?UTF-8?q?=EB=A6=AC=EB=A1=9C=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit list() 에서 항목마다 findTagNamesByEntryId() 를 개별 호출하던 N+1 을 findTagsByEntryIds() 배치 쿼리 1회로 대체. 항목 수가 많을 때 타임아웃/과부하 방지. Co-Authored-By: Claude Sonnet 4.6 --- .../web/account/mapper/AccountEntryMapper.java | 3 +++ .../sb/web/account/service/AccountService.java | 16 +++++++++++++--- src/main/resources/mapper/AccountEntryMapper.xml | 12 ++++++++++++ 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/sb/web/account/mapper/AccountEntryMapper.java b/src/main/java/com/sb/web/account/mapper/AccountEntryMapper.java index 107e633..1b1181f 100644 --- a/src/main/java/com/sb/web/account/mapper/AccountEntryMapper.java +++ b/src/main/java/com/sb/web/account/mapper/AccountEntryMapper.java @@ -93,4 +93,7 @@ public interface AccountEntryMapper { int deleteEntryTagsByEntryId(@Param("entryId") Long entryId); List findTagNamesByEntryId(@Param("entryId") Long entryId); + + /** 복수 항목의 태그를 한 번에 조회 — {entryId, tagName} 맵 반환 */ + List> findTagsByEntryIds(@Param("entryIds") List entryIds); } diff --git a/src/main/java/com/sb/web/account/service/AccountService.java b/src/main/java/com/sb/web/account/service/AccountService.java index 2868871..078b5e8 100644 --- a/src/main/java/com/sb/web/account/service/AccountService.java +++ b/src/main/java/com/sb/web/account/service/AccountService.java @@ -53,9 +53,19 @@ public class AccountService { public List list(Long memberId, Integer year, Integer month, String type, String category, Long walletId, String keyword, Long tagId) { - return mapper.findByMember(memberId, year, month, - blankToNull(type), blankToNull(category), walletId, blankToNull(keyword), tagId).stream() - .map(this::toResponse) + List entries = mapper.findByMember(memberId, year, month, + blankToNull(type), blankToNull(category), walletId, blankToNull(keyword), tagId); + if (entries.isEmpty()) return List.of(); + // 태그 N+1 방지: 한 번에 모든 entry 의 태그를 조회 후 Map 으로 그룹화 + List ids = entries.stream().map(AccountEntry::getId).toList(); + Map> tagsByEntry = new HashMap<>(); + for (Map row : mapper.findTagsByEntryIds(ids)) { + Long eid = ((Number) row.get("entryId")).longValue(); + String name = (String) row.get("tagName"); + tagsByEntry.computeIfAbsent(eid, k -> new ArrayList<>()).add(name); + } + return entries.stream() + .map(e -> AccountEntryResponse.from(e, tagsByEntry.getOrDefault(e.getId(), List.of()))) .toList(); } diff --git a/src/main/resources/mapper/AccountEntryMapper.xml b/src/main/resources/mapper/AccountEntryMapper.xml index 93f32d9..0150e41 100644 --- a/src/main/resources/mapper/AccountEntryMapper.xml +++ b/src/main/resources/mapper/AccountEntryMapper.xml @@ -234,4 +234,16 @@ ORDER BY t.name + + +