Projects/[Final] Shopping Mall Project

[기능구현] 최종 결제 및 주문 유일성 제약 적용하기 (2부)

montmer27 2026. 4. 27. 21:09

[관련 커밋 / PR]

https://github.com/all-in-market/mvp-api-server/pull/157

 

Feat/flyway by ginsengcandy · Pull Request #157 · all-in-market/mvp-api-server

개요 Flyway 도입 작업 내용 기존 데이터베이스 스키마 반영하여 sql ddl 작성 테스트 프로파일에서 Flyway 비활성화 기술적 고려사항 Liquibase와 비교 후 도입이 간편하다는 점에서 Flyway 적용함 참고

github.com

 

[이전 게시물]

https://montmer27.tistory.com/245

 

[기능구현] 최종 결제 및 주문 유일성 제약 적용하기 (1부)

[관련 커밋]https://github.com/all-in-market/mvp-api-server/pull/153 refactor: Payment 엔티티에서 merchantUid와 impUid 분리 by ginsengcandy · Pull Request #153 · all-in-market/mvp-api-ser개요 Payment의 최종 진실 확정 최종 성공 결

montmer27.tistory.com

 

[기술 스택]

  • Spring Boot 4.x
  • Java 21
  • PostgreSQL 17(Docker)

 

[지난 이야기]

주문 유일성 제약 적용 위해 partial unique index 도입을 결정했다.
하지만 이를 구현하기 위해서는 JPA 어노테이션으로는 WHERE 조건을 포함한 partial index를 표현할 수 없기 때문에, SQL 마이그레이션 툴 도입이 필요했다.
마침 데이터베이스 스키마 변경 이력을 추적하는 기능 도입도 검토 중이었기에 Flyway 도입을 결정했다
본문은 Flyway 도입 과정에서 발생한 일이다.

 

[목표]

- Flyway 도입

- 기존 데이터베이스 스키마 Flyway SQL DDL로 저장

- 이후 Payment 스키마 변경 내용 반영하여 V2(또는 그 이상) 저장

 

[실행 내용]

1단계: 의존성 추가

flyway-core와 flyway-database-postgresql을 추가했다. Flyway 10부터 PostgreSQL은 별도 모듈이 필요하다.

그러나 앱 기동 시 Flyway 관련 로그가 전혀 출력되지 않았고, Spring Boot 4.x에서는 spring-boot-starter-flyway가 필요하다는 것을 확인했다.

implementation 'org.springframework.boot:spring-boot-starter-flyway'

2단계: 순환 의존성 오류

Circular depends-on relationship between 'flyway' and 'entityManagerFactory'

application.yaml의 defer-datasource-initialization: true 설정이 Flyway와 순환 의존성을 유발하고 있었다. 해당 설정을 제거해 해결했다.

3단계: V1__init.sql 생성

로컬 DB에 테이블이 없어서 pg_dump 전에 먼저 ddl-auto: create로 앱을 한 번 실행해 테이블을 생성했다. 이후 Docker 컨테이너에서 덤프를 추출했다.

docker exec -t postgresql pg_dump --schema-only --no-owner -U postgres -d allinmarket > V1__init.sql

덤프 파일에서 제거한 항목은 두 가지다. pgAdmin 전용 디렉티브인 \restrict ... 와 \unrestrict ... 줄, 그리고 편집기에서 자동 포맷된 줄바꿈으로 깨진 SET 구문이다.

깨진 SET 구문 예시:

-- 잘못된 형태
SET
statement_timeout = 0;

-- 올바른 형태
SET statement_timeout = 0;

4단계: seller_dashboard 테이블 누락

앱 기동 시 Schema validation: missing table [seller_dashboard] 오류가 발생했다. pg_dump 당시 해당 테이블이 DB에 없었기 때문에 V1에 포함되지 않은 것이었다. V2 마이그레이션 파일로 분리해 추가했다.

5단계: V2 파일명 오타

V2를 추가했음에도 Flyway가 인식하지 못했다. DEBUG 로그를 활성화해 원인을 파악했다.

logging:
  level:
    org.flywaydb: DEBUG

로그에서 다음 출력을 확인했다.

Filtering out resource: db/migration/V2_add_seller_dashboard.sql

Flyway 파일명 규칙은 버전과 설명 사이에 언더스코어 2개(__) 가 필요하다. V2_add_seller_dashboard.sql을 V2__add_seller_dashboard.sql로 수정해 해결했다.

 

[결과]

Flyway가 V1, V2를 순서대로 실행했고 JPA validate도 통과해 앱이 정상 기동됐다. 최종 마이그레이션 파일 구조는 아래와 같다.

src/main/resources/db/migration/
├── V1__init.sql                       # 전체 스키마 기준점
└── V2__add_seller_dashboard.sql       # 누락 테이블 추가

향후 V3에서 payments 테이블에 partial unique index를 추가할 예정이다.

-- V3__add_partial_unique_index.sql
CREATE UNIQUE INDEX uq_payments_order_success
    ON payments (order_id)
    WHERE status = 'SUCCESS';

 

핵심 교훈 정리

Flyway 파일명은 V{버전}__{설명}.sql 형식이며 언더스코어 2개가 필수다.
Spring Boot 4.x에서는 spring-boot-starter-flyway를 사용해야 자동 구성이 연결된다.
pg_dump 결과물은 그대로 사용하지 않고 pgAdmin 전용 디렉티브와 줄바꿈 오류를 반드시 정리해야 한다.
Flyway가 동작하지 않을 때는 org.flywaydb: DEBUG 로그 레벨로 스캔 결과를 직접 확인하는 것이 가장 빠른 진단 방법이다.