176 lines
11 KiB
Markdown
176 lines
11 KiB
Markdown
---
|
|
schema_version: "2"
|
|
sid: "20260630-105459"
|
|
started_at: "2026-06-30T10:54:59"
|
|
ended_at: "2026-06-30T11:32:00"
|
|
user_request: "포스팅 메뉴 진입시 500 Error 발생해"
|
|
Summary: |
|
|
PostsMapper.listPublishedKeyset SQL에서 null 파라미터를 타입 없이 PostgreSQL에 전달해
|
|
PSQLException: could not determine data type of parameter $1 → 500 발생.
|
|
::bigint / ::timestamptz 명시 캐스트 추가로 수정.
|
|
update() SQL category_id 누락 버그 함께 수정. GET /posts 200 확인.
|
|
Invocations:
|
|
- agent: research-advisor
|
|
result: "포스팅 Controller/Mapper 구조 파악 + 500 원인 후보 목록 작성"
|
|
- agent: verification-advisor
|
|
result: "AC 4항목 전체 pass (HTTP 200, PSQLException 0건, SQL 캐스트 확인, category_id 확인)"
|
|
Decisions:
|
|
- "PSQLException $1 타입 미결정 → ::bigint / ::timestamptz 캐스트로 수정 (테이블 미존재 가설은 DDL 멱등 적용 후 NOTICE already-exists로 기각)"
|
|
- "update SQL category_id 누락 → 동일 파일 수정에 포함"
|
|
verified_by_me:
|
|
- "L1: compile pass (spring-boot:run 재시작 컴파일)"
|
|
- "L2: GET /posts → HTTP 200"
|
|
- "log scan: PSQLException 0건 (clean)"
|
|
needs_user_verification: "(없음)"
|
|
graph_refresh: "skip: no-graphify"
|
|
user_signals:
|
|
positive: []
|
|
negative: []
|
|
open_items:
|
|
- "회귀 테스트 미추가 (버그 수정 규약 미준수): PostControllerTest에 'null cursor 파라미터로 GET /posts 200' 시나리오 추가 필요. 후속 세션에서 처리."
|
|
commit: "168671b"
|
|
---
|
|
|
|
# Advisor Invocation Decision Log
|
|
# 각 advisor 호출/스킵 판단 즉시 1줄 append
|
|
|
|
- advisor: requirements-advisor
|
|
decision: skip
|
|
rationale: '요청 명확 — 포스팅 메뉴 진입 시 500 에러. 추가 요구사항 분해 불필요.'
|
|
checked_at: "2026-06-30T10:54:59"
|
|
|
|
- advisor: graphify-lookup-advisor
|
|
decision: skip
|
|
rationale: 'graphify add-on 없음. research-advisor 직접 호출.'
|
|
checked_at: "2026-06-30T10:54:59"
|
|
|
|
- advisor: design-advisor
|
|
decision: skip
|
|
rationale: '원인 확정 후 수정 경로 단일 — 파라미터 캐스트 + SQL 컬럼 추가. 설계 분기 없음.'
|
|
checked_at: "2026-06-30T11:20:00"
|
|
|
|
- advisor: implementation-advisor
|
|
decision: skip
|
|
rationale: '단일 파일 2-hunk 수정. orchestrator 직접 수행.'
|
|
checked_at: "2026-06-30T11:20:00"
|
|
|
|
---
|
|
|
|
Retrospective:
|
|
signals:
|
|
positive: []
|
|
negative: []
|
|
what_went_well:
|
|
- "초기 가설(테이블 미존재) DDL 멱등 적용 → NOTICE already-exists 로 빠르게 기각하고,
|
|
앱 로그 확인으로 실제 원인(PSQLException $1 타입 미결정)을 2단계 이내에 특정했다.
|
|
'가설 → DDL 검증 → 로그 확인' 순서가 500 버그 디버깅 효율 측면에서 유효했음."
|
|
- "단일 파일 2-hunk 수정(캐스트 추가 + category_id 누락)에 implementation-advisor를 거치지 않고
|
|
orchestrator가 직접 수행 — 불필요한 위임 없이 세션 시간을 절약."
|
|
- "두 번째 버그(update SQL category_id 누락)를 동일 파일 수정에 포함해 커밋 경계를 깔끔하게 유지."
|
|
what_to_improve:
|
|
- "PostsMapper.listPublishedKeyset 는 '#{param}::cast IS NULL' 패턴으로 null을 처리하는 반면,
|
|
JamsMapper·GamesMapper의 keyset 쿼리는 '<if test=\"... != null\">' 동적 XML 분기로 null을 회피한다.
|
|
두 패턴이 혼재하며, '::cast IS NULL' 패턴은 PostgreSQL이 prepared statement $1의 타입을 추론할 때
|
|
null 리터럴을 타입 미지정으로 전달하면 PSQLException이 발생한다는 함정이 있다.
|
|
이 패턴을 사용하는 매퍼는 반드시 명시 캐스트(::bigint / ::timestamptz 등)가 있어야 하며,
|
|
docs에 규약으로 기록하지 않으면 신규 매퍼 작성 시 동일 패턴 재발이 높다."
|
|
- "버그 재현 테스트(회귀 테스트) 없이 커밋됐다. verification-strategies.md 에는
|
|
'버그 수정 커밋은 해당 버그를 재현하는 테스트를 같이 포함한다'고 명시돼 있으나 이번 세션은 적용하지 않았다."
|
|
- "docker-compose.override.yml 존재 시 '도커 이미지 재빌드가 필요한 것 아닌가' 라는 혼선이 발생했다
|
|
(회고 포인트 3). override가 base image를 교체하기 때문에 docker compose up --build 는 override 환경에서
|
|
이미지 rebuild 후에도 override가 그 이미지를 다시 무시한다. local-dev-setup.md에 이 함정을
|
|
명시적 경고로 추가하지 않으면 재발 가능."
|
|
memory_candidates:
|
|
- name: mybatis-postgres-null-param-explicit-cast
|
|
type: feedback
|
|
description: "MyBatis + PostgreSQL에서 nullable 파라미터를 '#{p}::type IS NULL' 패턴으로 쓸 때 명시 캐스트 필수 — 미지정 시 PSQLException $N 타입 미결정"
|
|
body_draft: |
|
|
## Why
|
|
PostgreSQL은 prepared statement에서 null 리터럴의 타입을 추론할 수 없다.
|
|
MyBatis가 null 파라미터를 바인딩하면 $N의 타입이 결정되지 않아
|
|
`PSQLException: could not determine data type of parameter $1` 이 발생한다.
|
|
|
|
## 패턴 및 규약
|
|
nullable Long/OffsetDateTime 파라미터를 '#{p} IS NULL' 조건으로 쓸 때:
|
|
- **필수**: `#{p}::bigint`, `#{p}::timestamptz` 등 명시 캐스트 추가
|
|
- **대안**: `<if test="p != null">` 동적 XML 분기로 null 케이스 분리
|
|
|
|
현재 bibimbap 매퍼에는 두 패턴이 혼재함:
|
|
- PostsMapper.listPublishedKeyset → '::cast IS NULL' 패턴 (캐스트 추가로 수정됨 commit 168671b)
|
|
- JamsMapper.listVisibleKeyset, GamesMapper.listVisibleKeyset/searchVisibleKeyset → '<if test>' 분기 패턴
|
|
|
|
신규 keyset 페이징 매퍼 작성 시 '<if test>' 분기를 우선 권장.
|
|
'::cast IS NULL' 패턴을 쓴다면 명시 캐스트 누락 여부를 code review에서 확인한다.
|
|
|
|
## How to apply
|
|
1. nullable 파라미터를 IS NULL 조건으로 쓰는 MyBatis SQL을 rg로 전수 확인:
|
|
`rg '#{[^}]+}\s+IS\s+NULL' src/main/java --type java`
|
|
2. 각 라인에 `::type` 캐스트가 없으면 추가.
|
|
3. 또는 해당 조건 블록을 `<if test="p != null">` 으로 재작성.
|
|
rationale_for_saving: "동일 패턴 재발 가능성 높음 — 신규 페이징 매퍼 추가 시마다 잠재적으로 발생. 코드에서 유도 불가(컴파일 시점에 탐지 안 됨, L1 단위테스트도 @MockBean으로 회피)."
|
|
signal_source: observation
|
|
docs_sync_target: "/Users/wemadeplay/workspace/stz/bibimbap/docs/development/verification-strategies.md"
|
|
memory_optional: true
|
|
|
|
- name: docker-compose-override-masks-build
|
|
type: feedback
|
|
description: "docker-compose.override.yml 존재 시 'docker compose up --build' 로 Dockerfile 이미지를 새로 구워도 override가 base image를 교체하므로 빌드가 로컬 dev에 반영되지 않는다"
|
|
body_draft: |
|
|
## Why
|
|
`docker compose up --build app` 은 base `docker-compose.yml`의 `build:` 지시를 실행해
|
|
Dockerfile로 WAR를 굽는다. 그러나 `docker-compose.override.yml`이 존재하면
|
|
override가 `app` 서비스의 image/command/volumes를 재정의하므로
|
|
방금 구운 이미지는 override에 의해 무시된다.
|
|
|
|
## 규약
|
|
- 로컬 dev: `docker compose up -d app` (--build 불필요/무효)
|
|
코드 변경 반영: `docker compose restart app`
|
|
- 배포 이미지 빌드: override를 제외하고 base만 명시
|
|
`docker compose -f docker-compose.yml up -d --build app`
|
|
|
|
## How to apply
|
|
`--build` 를 쓰기 전에 `docker-compose.override.yml` 존재 여부 확인:
|
|
```bash
|
|
ls docker-compose.override.yml
|
|
```
|
|
존재하면 로컬 dev 경로(`restart`)를 사용하고,
|
|
Dockerfile 이미지가 필요한 경우 `-f docker-compose.yml` 로 override를 명시적으로 제외한다.
|
|
rationale_for_saving: "override 존재 사실 자체는 local-dev-setup.md에 있으나 '--build 무효' 함정이 명시되지 않아 재발 가능. 코드에서 유도 불가."
|
|
signal_source: observation
|
|
docs_sync_target: "/Users/wemadeplay/workspace/stz/bibimbap/docs/development/local-dev-setup.md"
|
|
memory_optional: true
|
|
|
|
- name: bug-fix-regression-test-required
|
|
type: feedback
|
|
description: "버그 수정 커밋에 재현 테스트 포함 의무(verification-strategies.md 기존 규약) — 이번 세션에서 미적용"
|
|
body_draft: |
|
|
## Why
|
|
verification-strategies.md §회귀 테스트 의무:
|
|
"버그 수정 커밋은 해당 버그를 재현하는 테스트를 같이 포함한다.
|
|
revert 시 테스트가 실패하고, 수정 후엔 통과해야 한다."
|
|
|
|
이번 세션(20260630-105459)에서 PSQLException 재현 테스트를 추가하지 않았다.
|
|
MyBatis + PostgreSQL 통합 계층이라 L1 단위테스트만으로는 재현이 어렵지만,
|
|
최소한 PostControllerTest에 "null cursor 파라미터로 GET /posts 200" 시나리오를 추가해야 함.
|
|
|
|
## How to apply
|
|
버그 수정 세션 종료 전 체크:
|
|
1. 버그 재현 조건(null cursor)을 트리거하는 테스트가 존재하는가?
|
|
2. 없으면 unit 레벨(mock) 테스트라도 추가 — 매퍼 호출 시 null 파라미터 경로 포함.
|
|
3. open_items에 "회귀 테스트 추가" 를 명시하고 후속 세션에서 처리.
|
|
rationale_for_saving: "기존 규약이 있음에도 이번 세션에서 미준수. open_items에도 없음 — 다음 세션이 이를 모르면 영구 누락 가능."
|
|
signal_source: observation
|
|
docs_sync_target: null
|
|
memory_optional: false
|
|
|
|
protocol_feedback:
|
|
- "버그 수정 세션에서 verification-advisor가 회귀 테스트 존재 여부를 AC 항목으로 체크하지 않았다.
|
|
verification-advisor 체크리스트에 '버그 수정 커밋 시 재현 테스트 존재 여부 확인' 항목을 추가 권고.
|
|
(verification-strategies.md §회귀 테스트 의무 조항이 verification-advisor의 실행 체크리스트에 연결되지 않음 — 구조적 갭.)"
|
|
- "연구 후보 가설을 DDL로 검증하는 과정(already-exists NOTICE로 기각)은 효율적이었지만,
|
|
프로토콜상 '초기 가설 검증 우선순위: 앱 로그 → DDL 검증' 순서를 명시하는 규약이 없다.
|
|
버그 디버깅 세션 체크리스트에 '먼저 앱 로그/에러 스택 확인 후 외부 가설(DB 스키마 등) 검증' 순서를 추가하면
|
|
DDL 적용 같은 부작용이 있는 검증 단계를 줄일 수 있다."
|
|
|
|
applied_changes: []
|