--- phase: research agent: research-advisor agent_version: 2 generated_at: 2026-06-29T06:17:17Z concerns: - "low source confidence — DDL(games.like_count default/NOT NULL, game_likes 제약)은 비권위 복원본(추론값). 운영 pg_dump 대조 전 권위 데이터로 승격 금지" concerns_checked: true source_confidence: mixed workers_spawned: 4 --- # 조사 결과 — 게임 "좋아요" 카운트 버그 근본 원인 ## 주제 게임 상세페이지에서 좋아요를 누르면 토글은 되지만, explore(탐색) 페이지로 가면 좋아요 수가 올라가지 않는다. 코드에서 근본 원인을 확정한다. ## 포인트별 발견 ### 포인트 1: 좋아요 토글 엔드포인트 — 서버 핸들러 자체가 없음 - 경로: `src/main/java/com/pandoli365/bibimbap/controller/api/GameController.java`, `.../mapper/GameLikesMapper.java`, `src/main/webapp/WEB-INF/views/game-detail.jsp` - 요약: 게임 좋아요 토글을 처리하는 **서버 엔드포인트가 존재하지 않는다.** GameController 매핑은 `/game/new`(POST), `/game/{id}`(GET), `/game/{id}/edit`(GET/POST), `/game/{id}`(DELETE)뿐이며 좋아요용 POST/PUT/DELETE 핸들러가 없다. `addGameLike`/`updateGameLike`/`getGameLike`(GameLikesMapper) 매퍼 메서드는 src 전체에서 **호출자 0건**(dead code). 상세 JSP 의 좋아요 버튼은 완전히 클라이언트 사이드로, `localStorage`('bibimbap-game-liked') 만 토글하고 화면 카운트를 `baseLikes + (liked ? 1 : 0)`로 다시 그릴 뿐 어떤 서버 fetch/ajax 도 호출하지 않는다. (advisor 직접 확인: game-detail.jsp:1480-1485 클릭 핸들러는 `setLiked()`+`syncLike()`만 호출.) - 관련 파일:라인: - GameController.java:63,132,169,196,268 (매핑 전체 — 좋아요 토글 매핑 없음) - GameLikesMapper.java:22,34,43 (getGameLike/addGameLike(INSERT INTO game_likes)/updateGameLike — 호출자 0건) - game-detail.jsp:1401-1408 (setLiked → localStorage 기록), :1473-1478 (syncLike → 로컬 ±1), :1480-1485 (클릭 핸들러, 서버 요청 없음) - 신뢰도: 확인됨 ### 포인트 1-b: GamesMapper 에 like_count 증감 UPDATE 메서드 부재 - 요약: GamesMapper 에 `incrementLikeCount`/`updateLikeCount`/`SET like_count` 류 UPDATE 메서드가 **존재하지 않는다**(검색 0 hit). UPDATE games 문은 `updateGame`(L149-164)과 `softDeleteGame`(L192-200) 둘뿐이며 어느 것도 `like_count`를 SET 하지 않는다. like_count 는 SELECT 절에만 등장. - 관련 파일:라인: GamesMapper.java:149-164, 192-200; increment 류 검색 0 hit - 신뢰도: 확인됨 ### 포인트 2: explore 목록 like 수 출처 — games.like_count 컬럼 직접 SELECT - 요약: 목록 조회 5개 메서드(getVisibleGames, searchVisibleGames, listVisibleKeyset, searchVisibleKeyset, searchGamesAdvanced) 전부 `g.like_count AS likeCount`(FROM games g)로 **컬럼을 직접 SELECT**한다. 어느 메서드도 `game_likes` 테이블을 COUNT 조인/서브쿼리로 집계하지 않는다. `game_likes`가 GamesMapper 에서 참조되는 곳은 게임 삭제용 `deleteGameLikes`(DELETE)뿐. `sort='likes'` 정렬도 `ORDER BY g.like_count DESC, g.id DESC`(L247)로 컬럼 사용. - 관련 파일:라인: GamesMapper.java:51 / 75 / 206 / 275 / 312 (각 메서드 SELECT 절의 `g.like_count AS likeCount`), :186-190 (deleteGameLikes) - 신뢰도: 확인됨 ### 포인트 3: 상세페이지 like 수 출처 — 동일하게 games.like_count 컬럼 - 요약: 상세 페이지도 explore 와 **같은 출처**(games.like_count 컬럼)를 읽는다. `GET /game/{id}` 핸들러가 `gamesMapper.getGame(id)`로 조회 → `getGame` SQL 은 `g.like_count AS likeCount`(games g JOIN users u 만, game_likes 미참조) → `addGameModel`이 `game.getLikeCount()`로 꺼내 `likeCount`/`likeCountFormatted` 모델 속성으로 주입 → JSP 가 `var baseLikes = ${likeCount};`로 기준선 사용. 좋아요 클릭 후 갱신은 **서버 재조회가 아니라 로컬 JS 증감(baseLikes ±1)** 일 뿐이다. - 관련 파일:라인: GameController.java:132,135,138,304,309-310; GamesMapper.java:28 (getGame `g.like_count AS likeCount`, JOIN users u 만); game-detail.jsp:1248(초기 표시), :1386(baseLikes), :1477(로컬 증감) - 신뢰도: 확인됨 ### 포인트 4: DDL/스키마 - 경로: `db/schema.sql` (단일 부트스트랩 파일) - 요약: `games.like_count` 컬럼은 **존재**한다 — `"like_count" integer DEFAULT 0 NOT NULL`(schema.sql:95). `game_likes` 테이블은 id(bigint PK), game_id(bigint NOT NULL FK→games), user_key(varchar(200) NOT NULL), created_at(timestamptz DEFAULT now()) 4컬럼, **`(game_id, user_key)` UNIQUE 제약 없음**(검색 0 hit). 단, schema.sql 헤더(4-11행)에 games/game_likes 는 "비권위 복원본 — 매퍼/POJO 에서 역추출, 타입·기본값·제약은 추론값" 경고가 명시. - 관련 파일:라인: db/schema.sql:95(like_count), :253-261(game_likes), :4-11(비권위 경고) - 신뢰도: 컬럼 존재 여부=확인됨 / default·NOT NULL·UNIQUE부재=추정(비권위 복원본, 운영 pg_dump 미대조) ## 종합 판단 네 포인트가 일관되게 수렴한다. explore 와 상세는 **동일한 출처**(`games.like_count` 컬럼)를 읽으며, 둘 다 실시간 `game_likes` COUNT 집계를 쓰지 않는다. 따라서 버그는 "explore 와 상세의 출처가 다르다"가 아니라 **좋아요 토글이 어떤 영속 저장소(DB)에도 쓰이지 않는다**는 데 있다. ### 근본 원인 가설 **1순위 (확정에 가까움): 좋아요는 DB 에 전혀 기록되지 않고 브라우저 localStorage 에만 저장된다.** - 상세 페이지 좋아요 버튼은 서버 엔드포인트를 호출하지 않고 localStorage('bibimbap-game-liked')만 토글한다(game-detail.jsp:1480-1485, 1401-1408). 좋아요 토글 서버 핸들러가 애초에 없다(GameController 매핑 0개, GameLikesMapper 호출자 0건). - 그 결과 `games.like_count` 컬럼도, `game_likes` 행도 갱신되지 않는다(GamesMapper 에 increment UPDATE 부재). explore 목록은 `games.like_count` 컬럼을 읽으므로 영원히 올라가지 않는다. - 증상 정합: 상세에서 "토글되어 보이는" 것은 같은 브라우저의 localStorage + 로컬 ±1 표시(baseLikes+1)일 뿐이고, explore(다른 페이지/다른 사용자/새로고침 후 DB값)에는 반영되지 않는다. 사용자가 보고한 "상세는 올라가 보이는데 explore 는 안 올라간다"와 정확히 일치. - 근거: GameController.java:63/132/169/196/268, GameLikesMapper.java:22/34/43(호출자 0), GamesMapper increment 부재, game-detail.jsp:1473-1485, GamesMapper.java:51/75/206/275/312(explore=컬럼) + :28(상세=컬럼). **2순위 (1순위에 포섭되는 보조 가설): 좋아요 영속화 백엔드 배선이 미완성/dead code 로 남아있다.** - GameLikesMapper(addGameLike/updateGameLike/getGameLike)와 games.like_count 컬럼은 좋아요를 DB 에 영속화하려던 설계 흔적이지만 컨트롤러/서비스 배선이 없어 dead code 다. 즉 기능이 "절반만 구현"된 상태. 만약 과거에 배선이 있었다면 회귀로 끊겼을 가능성(git 이력 확인은 본 조사 범위 밖). - 근거: GameLikesMapper.java:22/34/43(정의는 있으나 호출 0), GamesMapper.java:95(like_count 컬럼 존재) vs increment UPDATE 부재. 두 가설은 배타적이지 않다 — 1순위가 현상의 직접 원인이고, 2순위는 그 구조적 배경(미완성 영속화 계층)이다. ## 미해결 - 과거에 좋아요 영속화 배선(서버 토글 엔드포인트)이 존재했다가 제거/회귀된 것인지 여부 — git 이력 추적은 본 조사 범위 밖. (필요 시 후속 조사) - `games.like_count` 의 운영 DB 실제 default/제약, `game_likes (game_id,user_key)` UNIQUE 존재 여부 — schema.sql 이 비권위 복원본이라 미확정. 운영 pg_dump 대조 전 권위 데이터로 승격 금지. - index.jsp:843, profile.jsp:509 도 `game.getLikeCount()`(=컬럼)를 표시하나, 이 두 화면의 like 표시 정합성은 본 조사 4포인트 밖(관찰만).