Skip to content

Fix/sw 78 fix station name#88

Merged
casylm merged 3 commits intodevelopfrom
fix/SW-78-fix-station-name
Feb 11, 2026
Merged

Fix/sw 78 fix station name#88
casylm merged 3 commits intodevelopfrom
fix/SW-78-fix-station-name

Conversation

@Yongseok-2
Copy link
Copy Markdown
Collaborator

@Yongseok-2 Yongseok-2 commented Feb 11, 2026

PR 제목

당고개 -> 불암산 이름변경 및 경의중앙선 양평역 좌표 추가 및 주변 장소가 가장 많은 중간지점 추출

작업 유형

  • 새로운 기능 추가
  • 기존 기능 수정
  • 버그 수정
  • 리팩토링
  • 문서 업데이트
  • 테스트 추가/수정

작업 내용

  • 당고개 -> 불암산 이름변경
  • 경의중앙선 양평역 좌표 추가
  • 주변 장소가 가장 많은 중간지점 추출

체크리스트

  • 코드 컴파일 및 테스트 통과
  • 커밋 메시지 컨벤션 준수
  • 불필요한 디버깅 코드 제거

참고 사항 (선택)

  • 추가로 팀원이 참고할 내용

Summary by CodeRabbit

  • Bug Fixes

    • Updated station coordinates and corrected a station name for improved location accuracy.
  • New Features

    • Midpoint results now include nearby place counts and an "isHot" flag to highlight high-activity locations.
    • Midpoint search enriched with external place data and faster parallel lookups for quicker, more relevant suggestions.
    • Station name normalization added to improve coordinate matching across naming variants, including a new station entry.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Feb 11, 2026

📝 Walkthrough

Walkthrough

Adds Kakao place-count enrichment to midpoint computation: starts parallel route lookups and a Kakao place-count search, aggregates results (including placeCount and isHot), updates DTOs and station data, and normalizes station names for coordinate lookups.

Changes

Cohort / File(s) Summary
Async midpoint flow & place-count integration
src/main/java/swyp/mingling/domain/meeting/service/MidPointAsyncUseCase.java, src/main/java/swyp/mingling/domain/meeting/service/MidPointUseCase.java
Integrates SearchPlaceCountUseCase into the async midpoint pipeline; starts route lookups in parallel, includes Kakao place-count future in allOf, sets candidate.placeCount and isHot, adjusts route/endpoint payloads and station-name aliasing for coordinate cache lookups.
New Kakao place-count use-case
src/main/java/swyp/mingling/domain/meeting/service/SearchPlaceCountUseCase.java
New service mapping meeting purpose → category, building Kakao query and calling KakaoPlaceClient.search to retrieve place counts; contains internal Categorysub enum and throws BusinessException on unknown purpose.
DTOs and response model changes
src/main/java/swyp/mingling/domain/meeting/dto/response/midpoint/GetMidPointResponse.java, src/main/java/swyp/mingling/domain/meeting/dto/response/midpoint/MidPointCandidate.java, src/main/java/swyp/mingling/domain/subway/dto/SubwayRouteInfo.java
Adds isHot and placeCount to midpoint response and candidates; extends SubwayRouteInfo with placeCount; updates MidPointCandidate constructor/usage to include new fields and setter.
Repository addition
src/main/java/swyp/mingling/domain/meeting/repository/MeetingRepository.java
Adds findPurposeNamesByMeetingId(UUID meetingId) to fetch purpose name(s) for category mapping used by place searches.
RecommendPlace caching tweak
src/main/java/swyp/mingling/domain/meeting/service/RecommendPlaceUseCase.java
Replaces explicit cache configuration with generic @Cacheable (key/config details removed), import reordering only.
Station data
src/main/resources/stations_info.json
Updates coordinates for 신촌(2호선), renames 당고개불암산 (4호선), and adds 양평(경의중앙선) entry used by aliasing.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant MidPointAsync as MidPointAsyncUseCase
    participant Repo as MeetingRepository
    participant RouteSvc as RouteLookup (per-user routes)
    participant Kakao as SearchPlaceCountUseCase/KakaoPlaceClient
    participant Cache as StationCoordinateCache

    Client->>MidPointAsync: request midpoint(meetingId)
    MidPointAsync->>Repo: findPurposeNamesByMeetingId(meetingId)
    MidPointAsync->>Cache: normalize/search station coords (aliasing)
    MidPointAsync->>RouteSvc: start async route lookups for candidates (parallel)
    MidPointAsync->>Kakao: start async place-count search (midPlace, category)
    RouteSvc-->>MidPointAsync: route results (multiple futures)
    Kakao-->>MidPointAsync: place-count result
    MidPointAsync->>MidPointAsync: aggregate, dedupe, mark isHot/placeCount
    MidPointAsync-->>Client: GetMidPointResponse (includes isHot, placeCount)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • casylm
  • rkdrudgns0412

Poem

🐰 I hop through lines and alias names so bright,
Counting cozy spots by Kakao’s light.
Routes race in parallel, places chiming in—
Three warm midpoints found where city lights grin. 🥕✨

🚥 Pre-merge checks | ❌ 3
❌ Failed checks (2 warnings, 1 inconclusive)
Check name Status Explanation Resolution
Title check ⚠️ Warning The PR title 'Fix/sw 78 fix station name' only partially captures the changeset. While station name/coordinate updates are included, the title omits the major feature of extracting midpoints with the most nearby places, which represents significant functional changes. Update the title to reflect all major changes: e.g., 'Add nearby place count to midpoint selection and fix station data' or similar to encompass both the station fixes and the new place-counting feature.
Docstring Coverage ⚠️ Warning Docstring coverage is 44.44% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The PR description follows the template structure with work type, checklist items completed, and content sections filled. However, it significantly understates the scope: the commit message indicates a major feature ('extract midpoint with most nearby places'), but the description only lists it as a simple task with no detail about the asynchronous Kakao integration, new SearchPlaceCountUseCase, or response field additions. Expand the work content section with detailed explanations of the async Kakao place search integration, new SearchPlaceCountUseCase, and isHot/placeCount fields added to responses, so reviewers understand the full scope of changes.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/SW-78-fix-station-name

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In
`@src/main/java/swyp/mingling/domain/meeting/service/MidPointAsyncUseCase.java`:
- Around line 228-248: The alias condition uses the wrong line id so it never
matches: update the check in MidPointAsyncUseCase where you set searchName
(currently "경의선".equals(line)) to use "경의중앙선" (matching formatLineNumber()
output), and ensure the same aliasing logic (checking station.getStationName()
and station.getLineNumber() and substituting "양평(경의중앙선)") is applied inside the
transferPath mapping/stream as well; locate the searchName assignment and the
StationCoordinate lookup that calls findStationCoordinateUseCase.excute(...) and
replicate the corrected alias logic for transferPath processing so both direct
route and transfers resolve to the aliased name.

In `@src/main/resources/stations_info.json`:
- Around line 1148-1153: Update the coordinates for the station record where
"name": "불암산" and "line": "4호선": replace the existing latitude and longitude
values with the corrected coordinates latitude: 37.669583 and longitude:
127.0793667 so the JSON entry for 불암산 reflects the renamed station's actual
location.
🧹 Nitpick comments (1)
src/main/java/swyp/mingling/domain/meeting/service/MidPointAsyncUseCase.java (1)

228-237: Consider extracting the alias resolution into a reusable method.

The hardcoded special case for 양평 works as a targeted fix, but if more station name ambiguities arise (e.g., other stations sharing names across lines), a centralized alias map or method would be easier to maintain.

♻️ Suggested approach
+    // At class level or in a shared utility
+    private String resolveSearchName(String stationName, String lineName) {
+        if ("양평".equals(stationName) && "경의선".equals(lineName)) {
+            return "양평(경의중앙선)";
+        }
+        return stationName;
+    }

Then use resolveSearchName(name, line) in both the stations and transferPath mapping blocks.

Comment on lines +228 to 248
String name = station.getStationName();
String line = station.getLineNumber();

//검색어
String searchName = name;

//경의중앙선 양평 검색어 변경
if ("양평".equals(name) && "경의선".equals(line)) {
searchName = "양평(경의중앙선)";
}

StationCoordinate coord = stationCoordinateCache.computeIfAbsent(
station.getStationName(),
searchName,
stationName -> findStationCoordinateUseCase.excute(stationName)
);
return StationPathResponse.from(
station.getLineNumber(),
station.getStationName(),
line,
name,
coord.getLatitude(),
coord.getLongitude()
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check what line identifiers are used for 경의 in the codebase
echo "=== Searching for 경의선/경의중앙선 usage ==="
rg -n "경의선|경의중앙" --type java -C 2 | head -100

echo "=== Checking SubwayRouteInfo and related DTOs ==="
fd -e java | xargs rg -l "SubwayRouteInfo|getLineNumber" | head -10

echo "=== Check for line number patterns ==="
rg -n "getLineNumber|lineName|lineNumber" --type java -C 1 | head -80

Repository: SWYP-mingling/Backend

Length of output: 7440


🏁 Script executed:

#!/bin/bash
# Check the MidPointAsyncUseCase file structure around transferPath and alias logic
cat -n src/main/java/swyp/mingling/domain/meeting/service/MidPointAsyncUseCase.java | sed -n '200,250p'

Repository: SWYP-mingling/Backend

Length of output: 3638


🏁 Script executed:

#!/bin/bash
# Check if there are other places where the alias logic is applied
rg -n "양평.*경의중앙선|searchName|stationCoordinateCache" --type java -C 3 | head -100

Repository: SWYP-mingling/Backend

Length of output: 8005


🏁 Script executed:

#!/bin/bash
# Check SubwayRouteInfo.Transfer structure
cat -n src/main/java/swyp/mingling/domain/subway/dto/SubwayRouteInfo.java | sed -n '50,75p'

Repository: SWYP-mingling/Backend

Length of output: 496


🏁 Script executed:

#!/bin/bash
# Search for where "경의선" is actually used or set as a line name
rg -n '"경의선"' --type java -C 2

# Also check if there's test data that might clarify the expected value
rg -n "경의|양평" --type java -A 3 -B 3 | grep -E "(경의선|경의중앙선)" | head -20

Repository: SWYP-mingling/Backend

Length of output: 1453


🏁 Script executed:

#!/bin/bash
# Check the complete formatLineNumber method and how it processes special lines
cat -n src/main/java/swyp/mingling/domain/subway/parser/SeoulMetroRouteParser.java | sed -n '145,175p'

Repository: SWYP-mingling/Backend

Length of output: 956


Critical bug: The alias condition is checking for the wrong line identifier and will never execute.

Line 235 checks "경의선".equals(line), but formatLineNumber() (SeoulMetroRouteParser, line 149) returns "경의중앙선" for special lines. The condition will never match, so the alias never applies—양평 will always be looked up as the plain name "양평" instead of "양평(경의중앙선)", returning wrong coordinates.

Additionally, the transferPath stream (lines 210–223) does not apply this alias logic at all. If 양평 appears in a transfer path, it will also use the plain name and resolve to the wrong station.

Both the direct route and transfer paths need the alias applied to properly resolve 양평's coordinates on the 경의중앙선.

🤖 Prompt for AI Agents
In `@src/main/java/swyp/mingling/domain/meeting/service/MidPointAsyncUseCase.java`
around lines 228 - 248, The alias condition uses the wrong line id so it never
matches: update the check in MidPointAsyncUseCase where you set searchName
(currently "경의선".equals(line)) to use "경의중앙선" (matching formatLineNumber()
output), and ensure the same aliasing logic (checking station.getStationName()
and station.getLineNumber() and substituting "양평(경의중앙선)") is applied inside the
transferPath mapping/stream as well; locate the searchName assignment and the
StationCoordinate lookup that calls findStationCoordinateUseCase.excute(...) and
replicate the corrected alias logic for transferPath processing so both direct
route and transfers resolve to the aliased name.

Comment on lines 1148 to 1153
{
"line": "4호선",
"name": "당고개",
"name": "불암산",
"latitude": 37.670272,
"longitude": 127.079066
},
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if 당고개 still exists anywhere in the station data
rg -n "당고개" --type json

# Check all Line 4 stations, especially those mentioned in Jinjeop extension
rg -n "불암산|당고개|별내별가람|오남|진접|상계" --type json | head -50

Repository: SWYP-mingling/Backend

Length of output: 373


🏁 Script executed:

#!/bin/bash
# Check git history for 당고개
git log -p --all -- src/main/resources/stations_info.json | grep -C 5 "당고개" | head -50

# Check if 당고개 appears in any version
git log --all -S "당고개" -- src/main/resources/stations_info.json

Repository: SWYP-mingling/Backend

Length of output: 223


🌐 Web query:

Seoul Metro Line 4 Jinjeop extension stations 당고개 불암산 coordinates

💡 Result:

  • 당고개역 (Danggogae Station): 37.670272, 127.079047 (fr.wikipedia.org)
  • 불암산역 (Buramsan Station, Line 4 / station 409; renamed from 당고개): 37.669583, 127.0793667 (en.wikipedia.org)

Citations:


🏁 Script executed:

#!/bin/bash
# Get the Line 4 stations around the Jinjeop extension area to check coordinates
rg -A 4 '"line":\s*"4호선"' src/main/resources/stations_info.json | grep -A 4 -E '불암산|별내별가람|오남|진접|상계' | head -40

Repository: SWYP-mingling/Backend

Length of output: 475


Update coordinates for 불암산 to match the actual renamed station location.

The rename from 당고개 to 불암산 is correct—this is Seoul Metro's official renaming of the same station, not a replacement of distinct stations. However, the coordinates are incorrect: they reflect 당고개's old location (37.670272, 127.079047) rather than 불암산's actual location after the Jinjeop extension. Update to 37.669583, 127.0793667.

🤖 Prompt for AI Agents
In `@src/main/resources/stations_info.json` around lines 1148 - 1153, Update the
coordinates for the station record where "name": "불암산" and "line": "4호선":
replace the existing latitude and longitude values with the corrected
coordinates latitude: 37.669583 and longitude: 127.0793667 so the JSON entry for
불암산 reflects the renamed station's actual location.

Copy link
Copy Markdown
Collaborator

@casylm casylm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

확인완료

@casylm casylm merged commit bca9d7a into develop Feb 11, 2026
1 check was pending
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/main/java/swyp/mingling/domain/meeting/repository/MeetingRepository.java (1)

3-3: ⚠️ Potential issue | 🔴 Critical

Wrong @Param import — using Lettuce Redis annotation instead of Spring Data.

io.lettuce.core.dynamic.annotation.Param is from the Lettuce Redis client, not Spring Data JPA. The correct import is org.springframework.data.repository.query.Param. The Lettuce @Param won't be recognized by Spring Data's query method infrastructure, so parameter binding may fail at runtime (unless the compiler's -parameters flag is set, in which case Spring falls back to method-parameter-name detection — but that's fragile).

This same issue affects the existing findDeparturesAndNicknameByMeetingId method on line 24.

Proposed fix
-import io.lettuce.core.dynamic.annotation.Param;
+import org.springframework.data.repository.query.Param;
src/main/java/swyp/mingling/domain/meeting/service/MidPointUseCase.java (1)

170-178: ⚠️ Potential issue | 🟠 Major

Remove MidPointUseCase — it's unused dead code.

This entire class is not referenced anywhere in the codebase. Only MidPointAsyncUseCase is actively used (injected in MeetingController and called via .execute()). The MidPointUseCase builder omits isHot and placeCount fields, but this is moot since the code path is never executed. Delete this class to eliminate dead code and maintenance overhead.

🤖 Fix all issues with AI agents
In
`@src/main/java/swyp/mingling/domain/meeting/repository/MeetingRepository.java`:
- Around line 26-30: The query in MeetingRepository.findPurposeNamesByMeetingId
selects mp.name via the one-to-many association (Meeting -> purposeMappings ->
purpose mp) and can return multiple rows, so Optional<String> will fail on
multi-row results; change the repository method signature to return List<String>
(List of names) and update any callers to handle a list (e.g., join or pick
first), or alternatively modify the JPQL to restrict to a single result (e.g.,
fetch/limit) if only one purpose should ever be returned—apply the change to the
method findPurposeNamesByMeetingId and adjust call sites accordingly.

In
`@src/main/java/swyp/mingling/domain/meeting/service/MidPointAsyncUseCase.java`:
- Around line 76-77: The call to
meetingRepository.findPurposeNamesByMeetingId(meetingId) can return multiple
rows and throws IncorrectResultSizeDataAccessException; update
MidPointAsyncUseCase to call a repository method that returns a List (e.g.,
findPurposeNamesByMeetingId returning List<String>) or change the repo query to
aggregate, then handle the list safely: if empty use default "식당", if multiple
pick a deterministic value (e.g., first) or join them into a single category
string, and assign that result to the local variable category instead of relying
on .orElse.
- Around line 192-217: sortedByFairness currently limits to 2 entries which plus
hotnessSelection can yield only 2 unique items (and sortedByFairness.get(0) will
NPE if empty); update the logic in MidPointAsyncUseCase so that
candidates.stream().sorted(...).limit(3).toList() is used to produce a pool
(rename/keep using sortedByFairness), and compute hotnessSelection from
candidates safely (use .findFirst()/.max(...) with a fallback null check or
return an empty result early) instead of calling sortedByFairness.get(0); ensure
you only call hotnessSelection.setHot(true) when hotnessSelection != null and
then add items to a LinkedHashSet to dedupe, producing finalThree from that set
so you end up with up to 3 distinct MidPointCandidate entries.
- Around line 151-153: The code in MidPointAsyncUseCase uses
kakaoPlaceSearchResponse.join() and then calls place.getMeta().getTotalCount(),
which can NPE if meta is null; update the logic after KakaoPlaceSearchResponse
place = kakaoPlaceSearchResponse.join() to defensively check place.getMeta() for
null (or null-check place itself) and use a safe default (e.g., 0) for
placeCount when meta is null, then proceed with that safe placeCount value in
the rest of the method.

In
`@src/main/java/swyp/mingling/domain/meeting/service/RecommendPlaceUseCase.java`:
- Line 37: The `@Cacheable` annotation on RecommendPlaceUseCase is bare and will
throw at runtime; update the caching to specify a cache name (or add a
class-level `@CacheConfig`) so Spring can resolve a cache. For example, on the
method or the RecommendPlaceUseCase class replace the bare `@Cacheable` with
`@Cacheable`(cacheNames = "recommendPlace") (or set `@CacheConfig`(cacheNames =
"recommendPlace") at class level) ensuring the cache name matches your cache
configuration and any existing eviction/put usages.

In `@src/main/java/swyp/mingling/domain/subway/dto/SubwayRouteInfo.java`:
- Line 24: Remove the unused and semantically incorrect placeCount field from
the SubwayRouteInfo DTO: delete the private final Integer placeCount declaration
and any associated getter/constructor parameters or assignments in
SubwayRouteInfo, and ensure SeoulMetroRouteParser.parse() and any builders no
longer reference or attempt to set placeCount; if any callers expect placeCount,
update them to read place count from GetMidPointResponse/MidPointCandidate
instead.
🧹 Nitpick comments (4)
src/main/java/swyp/mingling/domain/meeting/service/SearchPlaceCountUseCase.java (2)

60-91: Categorysub is a full duplicate of Category in RecommendPlaceUseCase.

This inner enum has identical entries, fields, and from() logic as the Category enum in RecommendPlaceUseCase.java (lines 27-52). Consider extracting it into a shared top-level enum (e.g., MeetingCategory) to eliminate duplication and keep mappings consistent when categories change.


22-44: Inconsistent indentation and misleading log message.

  1. The "[CACHE MISS]" log label (line 24) is misleading — this class has no caching. If caching is planned, add a TODO; otherwise, remove the label.
  2. The method body has mixed indentation (4/5/8 spaces). Standardize to the project convention.
src/main/java/swyp/mingling/domain/meeting/service/MidPointAsyncUseCase.java (2)

219-222: Remove commented-out code.

This dead code adds noise. If the old List<List<SubwayRouteInfo>> approach is no longer needed, remove it rather than leaving it commented out.


126-141: Blocking I/O on the common ForkJoinPool.

Both the subway route API calls and the new Kakao place search call use CompletableFuture.supplyAsync() without a dedicated executor, running blocking HTTP calls on the shared ForkJoinPool.commonPool(). Under load, this can starve other tasks sharing that pool. This is a pre-existing pattern, but the addition of another blocking call per candidate (5 Kakao calls) amplifies the risk.

Consider providing a dedicated Executor (e.g., a fixed/cached thread pool) for these I/O-bound tasks.

Comment on lines +26 to +30
@Query("SELECT mp.name FROM Meeting m " +
"JOIN m.purposeMappings mpm " +
"JOIN mpm.purpose mp " +
"WHERE m.id = :meetingId")
Optional<String> findPurposeNamesByMeetingId(@Param("meetingId") UUID meetingId);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Optional<String> will throw if a meeting has multiple purposes.

The JPQL selects mp.name by joining through purposeMappings, which is a one-to-many relationship. If a meeting has more than one purpose, this query returns multiple rows, and Spring Data JPA will throw IncorrectResultSizeDataAccessException when attempting to wrap the result in Optional<String>.

Either change the return type to List<String> (and adjust the caller to pick one or join them), or add a LIMIT 1 / FETCH FIRST 1 ROWS ONLY to the JPQL if only one purpose is expected.

🤖 Prompt for AI Agents
In `@src/main/java/swyp/mingling/domain/meeting/repository/MeetingRepository.java`
around lines 26 - 30, The query in MeetingRepository.findPurposeNamesByMeetingId
selects mp.name via the one-to-many association (Meeting -> purposeMappings ->
purpose mp) and can return multiple rows, so Optional<String> will fail on
multi-row results; change the repository method signature to return List<String>
(List of names) and update any callers to handle a list (e.g., join or pick
first), or alternatively modify the JPQL to restrict to a single result (e.g.,
fetch/limit) if only one purpose should ever be returned—apply the change to the
method findPurposeNamesByMeetingId and adjust call sites accordingly.

Comment on lines +76 to +77
//카테고리 가져오기
String category = meetingRepository.findPurposeNamesByMeetingId(meetingId).orElse("식당");
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

findPurposeNamesByMeetingId will throw for meetings with multiple purposes.

As noted in the repository review, the query returns multiple rows for multi-purpose meetings, causing IncorrectResultSizeDataAccessException. The .orElse("식당") fallback only handles the no-result case, not the multiple-result case.

This will cause the entire midpoint computation to fail for any meeting with more than one purpose.

🤖 Prompt for AI Agents
In `@src/main/java/swyp/mingling/domain/meeting/service/MidPointAsyncUseCase.java`
around lines 76 - 77, The call to
meetingRepository.findPurposeNamesByMeetingId(meetingId) can return multiple
rows and throws IncorrectResultSizeDataAccessException; update
MidPointAsyncUseCase to call a repository method that returns a List (e.g.,
findPurposeNamesByMeetingId returning List<String>) or change the repo query to
aggregate, then handle the list safely: if empty use default "식당", if multiple
pick a deterministic value (e.g., first) or join them into a single category
string, and assign that result to the local variable category instead of relying
on .orElse.

Comment on lines +151 to +153
KakaoPlaceSearchResponse place = kakaoPlaceSearchResponse.join();

int placeCount = place.getMeta().getTotalCount();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Potential NPE if Kakao response has null meta.

place.getMeta().getTotalCount() will throw NullPointerException if the Kakao API returns a response with a null meta object. Consider adding a null check or providing a default.

Proposed fix
-                        int placeCount = place.getMeta().getTotalCount();
+                        int placeCount = (place != null && place.getMeta() != null)
+                                ? place.getMeta().getTotalCount() : 0;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
KakaoPlaceSearchResponse place = kakaoPlaceSearchResponse.join();
int placeCount = place.getMeta().getTotalCount();
KakaoPlaceSearchResponse place = kakaoPlaceSearchResponse.join();
int placeCount = (place != null && place.getMeta() != null)
? place.getMeta().getTotalCount() : 0;
🤖 Prompt for AI Agents
In `@src/main/java/swyp/mingling/domain/meeting/service/MidPointAsyncUseCase.java`
around lines 151 - 153, The code in MidPointAsyncUseCase uses
kakaoPlaceSearchResponse.join() and then calls place.getMeta().getTotalCount(),
which can NPE if meta is null; update the logic after KakaoPlaceSearchResponse
place = kakaoPlaceSearchResponse.join() to defensively check place.getMeta() for
null (or null-check place itself) and use a safe default (e.g., 0) for
placeCount when meta is null, then proceed with that safe placeCount value in
the rest of the method.

Comment on lines +192 to +217
// 이동시간 + 편차 고려한 리스트 중 2개 추출
List<MidPointCandidate> sortedByFairness =
candidates.stream()
.sorted(
Comparator.comparing(MidPointCandidate::getDeviation)
.thenComparing(MidPointCandidate::getAvgTime)
)
.limit(3)
.map(MidPointCandidate::getRoutes)
.limit(2)
.toList();

// 장소 개수 기준 상위 1개 추출 (중복 여부 상관없이 1위 추출)
MidPointCandidate hotnessSelection = candidates.stream()
.sorted(Comparator.comparing(MidPointCandidate::getPlaceCount).reversed())
.findFirst()
.orElse(sortedByFairness.get(0)); // 만약 리스트가 비어있을 경우 대비

// 장소가 가장많은 중간지점은 Hot = true
hotnessSelection.setHot(true);

// 중복 제거를 위한 LinkedHashSet
Set<MidPointCandidate> set = new LinkedHashSet<>();
// 중간지점 추가
set.addAll(sortedByFairness);
set.add(hotnessSelection);

List<MidPointCandidate> finalThree = new ArrayList<>(set);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Final candidate list may have only 2 entries if the "hottest" candidate is also one of the 2 fairest.

LinkedHashSet deduplication by reference identity means that if hotnessSelection is the same object as one already in sortedByFairness, finalThree will contain only 2 candidates instead of 3. If this is intentional, consider adding a comment; otherwise, the limit(2) on line 199 may need to be limit(3) as a pool from which 3 distinct candidates are drawn.

Also, if candidates is empty (e.g., no hot places in DB), sortedByFairness is empty and sortedByFairness.get(0) on line 206 will throw IndexOutOfBoundsException.

🤖 Prompt for AI Agents
In `@src/main/java/swyp/mingling/domain/meeting/service/MidPointAsyncUseCase.java`
around lines 192 - 217, sortedByFairness currently limits to 2 entries which
plus hotnessSelection can yield only 2 unique items (and sortedByFairness.get(0)
will NPE if empty); update the logic in MidPointAsyncUseCase so that
candidates.stream().sorted(...).limit(3).toList() is used to produce a pool
(rename/keep using sortedByFairness), and compute hotnessSelection from
candidates safely (use .findFirst()/.max(...) with a fallback null check or
return an empty result early) instead of calling sortedByFairness.get(0); ensure
you only call hotnessSelection.setHot(true) when hotnessSelection != null and
then add items to a LinkedHashSet to dedupe, producing finalThree from that set
so you end up with up to 3 distinct MidPointCandidate entries.

cacheManager = "placeCacheManager",
key = "'recommend:' + #midPlace + ':' + #category + ':' + #page + ':' + #size"
)
@Cacheable
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash

# First, find the file and check its current content around line 37
echo "=== Current file content around line 37 ==="
cat -n 'src/main/java/swyp/mingling/domain/meeting/service/RecommendPlaceUseCase.java' | sed -n '30,50p'

echo -e "\n=== Checking for `@CacheConfig` at class level ==="
cat -n 'src/main/java/swyp/mingling/domain/meeting/service/RecommendPlaceUseCase.java' | head -40

echo -e "\n=== Looking for `@CacheConfig` anywhere in the codebase ==="
rg -n '@CacheConfig' --type=java

echo -e "\n=== Git history of the file ==="
git log --oneline -- 'src/main/java/swyp/mingling/domain/meeting/service/RecommendPlaceUseCase.java' | head -10

echo -e "\n=== Recent changes to this specific line ==="
git log -p -- 'src/main/java/swyp/mingling/domain/meeting/service/RecommendPlaceUseCase.java' | head -150

Repository: SWYP-mingling/Backend

Length of output: 2734


🏁 Script executed:

#!/bin/bash

echo "=== Full git show output for the commit ==="
git show 8674956 --stat

echo -e "\n=== Git show for specific file ==="
git show 8674956 -- 'src/main/java/swyp/mingling/domain/meeting/service/RecommendPlaceUseCase.java' | head -200

echo -e "\n=== Check if file existed before this commit ==="
git log --all --full-history -- 'src/main/java/swyp/mingling/domain/meeting/service/RecommendPlaceUseCase.java' | head -30

Repository: SWYP-mingling/Backend

Length of output: 13435


🌐 Web query:

Spring @Cacheable annotation requires cacheNames value IllegalStateException runtime error

💡 Result:

This exception happens when Spring’s caching interceptor can’t resolve any target cache for a @Cacheable operation—most commonly because @Cacheable has no cacheNames / value (so it ends up as caches=[]), and you also didn’t configure a custom cacheResolver. Spring then throws IllegalStateException: No cache could be resolved… At least one cache should be provided per cache operation. [1][2]

Fix options

  1. Specify a cache name on the annotation (most common fix)
`@Cacheable`(cacheNames = "users")   // or: `@Cacheable`("users")
public User getUser(Long id) { ... }

value is an alias for cacheNames. [3]

  1. Define a default cache name at class level
`@CacheConfig`(cacheNames = "users")
public class UserService {
  `@Cacheable`
  public User getUser(Long id) { ... }
}
  1. If you intentionally omitted cacheNames, provide a cacheResolver
    Spring docs note that since Spring 4.1 the cache name can be omitted if you supply a CacheResolver, but without one the default resolution expects cache names. [2]

  2. Make sure the cache name actually exists / matches your cache provider config
    If your cache provider (Ehcache/Redis/etc.) uses a different cache name than your @Cacheable("..."), you can still hit resolution problems; using the same name resolves it. [4]

Sources: Stack Overflow explanations and examples of the exact exception [1][4], Spring Framework reference docs on cache name requirement and cacheResolver behavior [2], and @Cacheable Javadoc showing valuecacheNames aliasing [3].


Fix bare @Cacheable — missing required cacheNames parameter will fail at runtime.

Spring's @Cacheable requires at least one cache name via cacheNames or value. Without it, Spring throws IllegalStateException: No cache could be resolved… at the first method invocation. There is no class-level @CacheConfig providing defaults, so the annotation must specify cache configuration explicitly.

Proposed fix
-    `@Cacheable`
+    `@Cacheable`(cacheNames = "recommendPlace", key = "#midPlace + '_' + `#category` + '_' + `#page` + '_' + `#size`")
🤖 Prompt for AI Agents
In
`@src/main/java/swyp/mingling/domain/meeting/service/RecommendPlaceUseCase.java`
at line 37, The `@Cacheable` annotation on RecommendPlaceUseCase is bare and will
throw at runtime; update the caching to specify a cache name (or add a
class-level `@CacheConfig`) so Spring can resolve a cache. For example, on the
method or the RecommendPlaceUseCase class replace the bare `@Cacheable` with
`@Cacheable`(cacheNames = "recommendPlace") (or set `@CacheConfig`(cacheNames =
"recommendPlace") at class level) ensuring the cache name matches your cache
configuration and any existing eviction/put usages.

private final String endStation;
private final String endStationLine;
private final Integer totalTravelTime;
private final Integer placeCount;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# Check if placeCount is ever set on SubwayRouteInfo (builder call or direct set)
rg "\.placeCount\(" --type java -C 2 | head -50

Repository: SWYP-mingling/Backend

Length of output: 707


🏁 Script executed:

# Check if SubwayRouteInfo.getPlaceCount() is called anywhere
rg "getPlaceCount\(\)" --type java -C 2 | head -30

Repository: SWYP-mingling/Backend

Length of output: 700


🏁 Script executed:

# Find SubwayRouteInfo class definition to check field and builders
fd "SubwayRouteInfo.java" --type f

Repository: SWYP-mingling/Backend

Length of output: 131


🏁 Script executed:

# Check MidPointCandidate for placeCount field
fd "MidPointCandidate.java" --type f

Repository: SWYP-mingling/Backend

Length of output: 152


🏁 Script executed:

# Read SubwayRouteInfo class to see the field definition
cat -n src/main/java/swyp/mingling/domain/subway/dto/SubwayRouteInfo.java

Repository: SWYP-mingling/Backend

Length of output: 2155


🏁 Script executed:

# Get context around the getPlaceCount() call in MidPointAsyncUseCase
rg -n "getPlaceCount\(\)" src/main/java/swyp/mingling/domain/meeting/service/MidPointAsyncUseCase.java -B 5 -A 5

Repository: SWYP-mingling/Backend

Length of output: 774


🏁 Script executed:

# Find the type of routeList variable
rg -n "routeList\." src/main/java/swyp/mingling/domain/meeting/service/MidPointAsyncUseCase.java -B 3 | head -40

Repository: SWYP-mingling/Backend

Length of output: 898


🏁 Script executed:

# Read MidPointCandidate to see what fields it has
cat -n src/main/java/swyp/mingling/domain/meeting/dto/response/midpoint/MidPointCandidate.java

Repository: SWYP-mingling/Backend

Length of output: 835


🏁 Script executed:

# Find GetMidPointResponse to understand the relationship
fd "GetMidPointResponse.java" --type f

Repository: SWYP-mingling/Backend

Length of output: 154


🏁 Script executed:

# Search for where SubwayRouteInfo is constructed with .placeCount() builder
rg "SubwayRouteInfo.*builder|SubwayRouteInfo\.builder" --type java -A 20 | grep -E "(SubwayRouteInfo|placeCount|\.build\(\))" | head -50

Repository: SWYP-mingling/Backend

Length of output: 2266


🏁 Script executed:

# Check SeoulMetroRouteParser to see how SubwayRouteInfo is built
fd "SeoulMetroRouteParser.java" --type f

Repository: SWYP-mingling/Backend

Length of output: 140


🏁 Script executed:

# Read GetMidPointResponse to understand the structure
cat -n src/main/java/swyp/mingling/domain/meeting/dto/response/midpoint/GetMidPointResponse.java

Repository: SWYP-mingling/Backend

Length of output: 1126


🏁 Script executed:

# Read SeoulMetroRouteParser to see how SubwayRouteInfo is built
cat -n src/main/java/swyp/mingling/domain/subway/parser/SeoulMetroRouteParser.java | head -100

Repository: SWYP-mingling/Backend

Length of output: 4606


🏁 Script executed:

# Get more detail on SubwayRouteInfo builder calls in SeoulMetroRouteParser
rg "SubwayRouteInfo\.builder" src/main/java/swyp/mingling/domain/subway/parser/SeoulMetroRouteParser.java -A 10 | head -40

Repository: SWYP-mingling/Backend

Length of output: 531


🏁 Script executed:

# Check where placeCount might be set on SubwayRouteInfo instances
rg "\.placeCount\(" src/main/java/swyp/mingling/domain --type java -B 3 -A 1

Repository: SWYP-mingling/Backend

Length of output: 738


🏁 Script executed:

# Double-check: search for any reading of SubwayRouteInfo.placeCount specifically
rg "route.*getPlaceCount|SubwayRouteInfo.*placeCount" --type java -B 2 -A 2

Repository: SWYP-mingling/Backend

Length of output: 700


placeCount field is unused and semantically misplaced on SubwayRouteInfo.

This field represents the count of nearby places at a destination, which is a property of the midpoint candidate (available on GetMidPointResponse and MidPointCandidate), not of an individual subway route. The field is never set when building SubwayRouteInfo instances (neither in SeoulMetroRouteParser.parse() nor in any other builder), and it's never read from route objects—only from GetMidPointResponse at the destination level.

Remove this field to avoid confusion about what it represents and prevent future maintainers from mistakenly trying to populate it with route-specific data.

🤖 Prompt for AI Agents
In `@src/main/java/swyp/mingling/domain/subway/dto/SubwayRouteInfo.java` at line
24, Remove the unused and semantically incorrect placeCount field from the
SubwayRouteInfo DTO: delete the private final Integer placeCount declaration and
any associated getter/constructor parameters or assignments in SubwayRouteInfo,
and ensure SeoulMetroRouteParser.parse() and any builders no longer reference or
attempt to set placeCount; if any callers expect placeCount, update them to read
place count from GetMidPointResponse/MidPointCandidate instead.

@Yongseok-2 Yongseok-2 deleted the fix/SW-78-fix-station-name branch February 14, 2026 11:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants