-
Notifications
You must be signed in to change notification settings - Fork 1
chore: Flyway 기반 데이터베이스 스키마 형상관리 활성화 #109
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
…eading_records, tags, reading_record_tags 테이블 추가 및 제약조건)
WalkthroughFlyway 기반 마이그레이션/시드 구조와 거버넌스 문서를 추가하고, 공용/개발용 persistence 설정을 Flyway + JPA validate로 정비했다. 초기 MySQL 스키마 마이그레이션과 기본 태그 시드를 추가했으며, BookEntity와 UserBookEntity의 컬럼 길이 제약을 조정했다. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Assessment against linked issues
Assessment against linked issues: Out-of-scope changes
Tip 🔌 Remote MCP (Model Context Protocol) integration is now available!Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats. ✨ Finishing Touches
🧪 Generate unit tests
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
Status, Documentation and Community
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (6)
infra/src/main/kotlin/org/yapp/infra/book/entity/BookEntity.kt (1)
17-18: @SQLDelete의 WHERE 컬럼명이 잘못되었습니다 (isbn → id 또는 isbn13).books 테이블에는 isbn 컬럼이 존재하지 않습니다. 현재 SQLDelete는 "WHERE isbn = ?"로 되어 있어 런타임에 SQLSyntaxErrorException이 발생합니다. 기본적으로 파라미터에는 엔티티의 식별자(id)가 바인딩되므로 id 컬럼을 사용하는 것이 안전합니다. 또한 타임스탬프 정밀도 일관성을 위해 NOW(6)를 권장합니다.
다음과 같이 수정해 주세요:
-@SQLDelete(sql = "UPDATE books SET deleted_at = NOW() WHERE isbn = ?") +@SQLDelete(sql = "UPDATE books SET deleted_at = NOW(6) WHERE id = ?")infra/README.md (1)
55-111: Markdown Lint 위반(MD022/MD032/MD007/MD009/MD037) 해결 필요헤딩 앞뒤 공백, 리스트 앞뒤 공백, 중첩 리스트 들여쓰기(4칸), 일부 트레일링 스페이스, 강조 마커 내부 공백 등의 이슈가 보고되었습니다. 문서 렌더링 일관성과 CI 안정성을 위해 아래처럼 정리해 주세요.
다음과 같이 대표 구간을 수정할 수 있습니다(요지: 헤딩 전후 빈 줄 추가, 리스트 전후 빈 줄 추가, 중첩 리스트 4칸 들여쓰기, 트레일링 스페이스 제거):
-## **💿Database Schema Governance Strategy** +## **💿Database Schema Governance Strategy** + - 개발 환경에서는 ddl-auto를 활용해 스키마를 신속히 반영하고, 운영 환경에서는 Flyway 기반 마이그레이션을 통해 데이터베이스 스키마를 형상관리한다. - Flyway 스크립트는 스키마 구조를 다루는 migration과 초기 데이터를 다루는 seed로 역할을 분리하여 관리한다. -### 1. 스키마 관리 (migration) +### 1. 스키마 관리 (migration) + - 스크립트 위치는 infra 모듈의 resources/db/migration 디렉터리에 저장한다. - 스크립트는 `V<YYYYMMDD>_<NNN>__<Action>_<Object>_<Details>.sql` 형식으로 명명한다. - 구체적인 네이밍 규칙은 아래와 같다. - - 예시: V - - 예시: 20250820 - - 예시: _001, _002 + - 예시: V + - 예시: 20250820 + - 예시: _001, _002 - __ (구분자): 버전과 설명을 구분하는 이중 밑줄 표준 구분자이다. - `<Action>` (동작): 스크립트가 수행하는 주요 DDL 동작을 대문자로 명시한다. - - 예시: CREATE, ALTER, ADD, DROP, RENAME, INSERT + - 예시: CREATE, ALTER, ADD, DROP, RENAME, INSERT - `<Object>` (객체): 변경이 일어나는 주된 객체의 이름으로, 테이블 이름을 뜻한다. - - 예시: USERS, BOOKS, USER_BOOKS + - 예시: USERS, BOOKS, USER_BOOKS - `<Details>` (상세 설명): 변경 사항을 더 구체적으로 설명한다. 여러 단어는 밑줄(_)로 연결하고, 첫 글자만 대문자로 표기한다. - - 예시: mail_Column, Add_Nickname, Fk_To_Users + - 예시: Mail_Column, Add_Nickname, Fk_To_Users @@ -### 2. 초기 데이터 관리 (seed) +### 2. 초기 데이터 관리 (seed) + - 스크립트 위치는 infra 모듈의 resources/db/seed 디렉터리 구조로 관리한다. - `db/seed/common/`: 모든 환경(개발, 스테이징, 운영)에 필요한 필수 기본 데이터 - `db/seed/dev/`: 개발 환경 전용 테스트 데이터 (향후 필요시 적용) - 스크립트는 `R__<Object>_<Data_Type>.sql` 형식으로 명명한다. - 구체적인 네이밍 규칙은 아래와 같다. -- `R` (접두사): Repeatable Migration을 의미하는 Flyway의 표준 접두사로, 스크립트 내용이 변경될 때마다 다시 실행된다. - - 예시: R -__ (구분자): 접두사와 설명을 구분하는 이중 밑줄 표준 구분자이다. +- `R` (접두사): Repeatable Migration을 의미하는 Flyway의 표준 접두사로, 스크립트 내용이 변경될 때마다 다시 실행된다. + - 예시: R +- __ (구분자): 접두사와 설명을 구분하는 이중 밑줄 표준 구분자이다. - `<Object>` (객체): 데이터가 삽입되는 주된 테이블 이름을 소문자로 명시한다. - 예시: tags, roles, categories - `<Data_Type>` (데이터 유형): 삽입되는 데이터의 성격을 설명한다. 여러 단어는 밑줄(_)로 연결하고, 소문자로 표기한다. - 예시: default_data, master_data, essential_data @@ -#### 데이터 삽입 가이드라인 +#### 데이터 삽입 가이드라인 + - **기본 원칙**: 모든 초기 데이터는 R__ 스크립트를 사용하여 관리한다. - **중복 방지**: INSERT IGNORE 또는 ON DUPLICATE KEY UPDATE 구문을 활용하여 중복 삽입을 방지한다. - **성능 고려**: 대용량 데이터(만 건 이상)는 R__ 스크립트 사용을 지양하고, 별도 프로세스로 관리한다.infra/src/main/resources/db/migration/mysql/V1__init.sql (1)
1-95: 외래 키 부재: 의도 확인 필요FK를 배제해 애플리케이션 레벨에서 정합성을 관리하려는 의도일 수 있으나, 삭제/갱신 시 데이터 고아 발생을 방지하려면 최소한 read path 최적화를 위한 인덱스와 함께 FK(또는 주기적 검증 작업)가 필요합니다.
- 정책적으로 FK 미사용이 맞는지 확인 부탁드립니다. 맞다면 정합성 검증 배치/헬스체크를 준비하는 것이 좋습니다. 원하시면 스크립트/배치 예시를 제공하겠습니다.
또한 테이블별 ENGINE/CHARSET/COLLATION을 명시(예: ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci)해 환경별 기본값 차이에 따른 이슈를 예방하는 것을 권장합니다.
infra/src/main/resources/application-persistence.yml (3)
12-12: show-sql는 운영 기본 설정에서 비활성화하는 것이 안전합니다.상위(기본) 프로필에 show-sql: true가 설정되어 있어 운영 환경에서도 SQL 로그가 노출될 수 있습니다. dev 프로필로 이동시키는 것을 권장합니다.
다음과 같이 조정해 주세요:
spring: jpa: database-platform: org.hibernate.dialect.MySQL8Dialect hibernate: ddl-auto: none - show-sql: true open-in-view: false @@ --- spring: config: activate: on-profile: dev jpa: hibernate: ddl-auto: validate + show-sql: true
49-52: test 프로필의 driver-class-name 빈 값은 제거하세요.빈 값으로 설정하면 자동 감지가 비활성화될 수 있습니다. Testcontainers 사용 시 해당 키를 완전히 제거하는 것이 안전합니다(주석만 남기세요).
datasource: url: jdbc:tc:mysql:8.0.39:///testdb - driver-class-name: # Spring Boot의 Testcontainers 자동 설정 기능 동작 -> Testcontainers에 내장된 드라이버 동적 주입 + # driver-class-name: Spring Boot의 Testcontainers 자동 설정 기능 동작 -> Testcontainers에 내장된 드라이버 동적 주입
9-11: MySQL Dialect 자동 감지 활용을 고려(선택 사항)Hibernate 6 + Spring Boot 3에서는 Dialect 자동 감지가 안정적으로 동작합니다. 명시가 필요한 특수 케이스가 아니라면 설정 간소화를 위해 database-platform 생략을 고려해 볼 수 있습니다.
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (5)
infra/README.md(1 hunks)infra/src/main/kotlin/org/yapp/infra/book/entity/BookEntity.kt(1 hunks)infra/src/main/resources/application-persistence.yml(2 hunks)infra/src/main/resources/db/migration/mysql/V1__init.sql(1 hunks)infra/src/main/resources/db/seed/common/R__tags_default_data.sql(1 hunks)
🧰 Additional context used
🪛 markdownlint-cli2 (0.17.2)
infra/README.md
55-55: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
56-56: Lists should be surrounded by blank lines
(MD032, blanks-around-lists)
59-59: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
60-60: Lists should be surrounded by blank lines
(MD032, blanks-around-lists)
64-64: Unordered list indentation
Expected: 4; Actual: 6
(MD007, ul-indent)
66-66: Unordered list indentation
Expected: 4; Actual: 6
(MD007, ul-indent)
66-66: Trailing spaces
Expected: 0 or 2; Actual: 1
(MD009, no-trailing-spaces)
68-68: Unordered list indentation
Expected: 4; Actual: 6
(MD007, ul-indent)
68-68: Spaces inside emphasis markers
(MD037, no-space-in-emphasis)
71-71: Unordered list indentation
Expected: 4; Actual: 6
(MD007, ul-indent)
73-73: Unordered list indentation
Expected: 4; Actual: 6
(MD007, ul-indent)
73-73: Trailing spaces
Expected: 0 or 2; Actual: 1
(MD009, no-trailing-spaces)
75-75: Unordered list indentation
Expected: 4; Actual: 6
(MD007, ul-indent)
86-86: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
87-87: Lists should be surrounded by blank lines
(MD032, blanks-around-lists)
107-107: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
108-108: Lists should be surrounded by blank lines
(MD032, blanks-around-lists)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: build-validation
- GitHub Check: label-pr
🔇 Additional comments (4)
infra/src/main/kotlin/org/yapp/infra/book/entity/BookEntity.kt (1)
44-46: publisher 길이 확장(255 → 300) 적절합니다.마이그레이션(V1__init.sql)의 books.publisher VARCHAR(300)와 일치하여 Flyway/JPA validate 시에도 일관성이 유지됩니다.
infra/src/main/resources/db/migration/mysql/V1__init.sql (1)
25-31: books.publisher 길이(300)와 엔티티 매핑이 일치합니다.BookEntity의 @column(length = 300)과 정확히 일치합니다. Flyway/JPA validate 시에도 무리 없습니다.
infra/src/main/resources/db/seed/common/R__tags_default_data.sql (1)
1-7: Repeatable Seed 설계 적절합니다.
- 명시적 UUID + 이름 유니크 제약과 ON DUPLICATE KEY UPDATE로 멱등성을 확보했습니다.
- updated_at을 NOW(6)로 갱신해 재실행 시 추적성도 좋습니다.
운영 환경에서도 안전한 기본 데이터로 보이며, README에 정의한 거버넌스와도 일치합니다.
infra/src/main/resources/application-persistence.yml (1)
20-25: baseline-on-migrate=false의 운영 첫 도입 시 주의기존 스키마가 존재하는 DB에 Flyway를 처음 활성화할 때 baseline-on-migrate=false면 마이그레이션이 실패합니다. 운영/스테이징 DB의 초기 Baseline 적용 전략(수동 baseline, 스크립트, 또는 일시적 프로퍼티 변경)을 문서화/자동화해 주세요.
원하시면 환경별 Baseline 점검 스크립트를 제공하겠습니다.
Also applies to: 37-42
| flyway: | ||
| enabled: true | ||
| baseline-on-migrate: true | ||
| baseline-version: 0 | ||
| clean-disabled: true | ||
| baseline-on-migrate: false | ||
| validate-on-migrate: true | ||
| locations: | ||
| - classpath:db/migration | ||
| - classpath:db/reed | ||
| - classpath:db/migration/mysql | ||
| - classpath:db/seed/common | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Flyway clean 방지 설정을 명시해 주세요.
실수로 clean이 수행되는 사고를 예방하기 위해 기본 프로필에 clean-disabled: true를 명시하는 것을 권장합니다.
flyway:
enabled: true
baseline-on-migrate: false
validate-on-migrate: true
+ clean-disabled: true
locations:
- classpath:db/migration/mysql
- classpath:db/seed/common🤖 Prompt for AI Agents
In infra/src/main/resources/application-persistence.yml around lines 18 to 25,
the Flyway config lacks an explicit setting to prevent accidental clean
operations; add clean-disabled: true under the flyway block in the default
profile (alongside enabled, baseline-on-migrate, validate-on-migrate, and
locations) so Flyway clean is disabled by default to prevent destructive
accidental runs.
| email VARCHAR(100) NOT NULL, | ||
| provider_type ENUM ('APPLE', 'KAKAO') NOT NULL, | ||
| provider_id VARCHAR(100) NOT NULL, | ||
| nickname VARCHAR(100) NOT NULL, | ||
| profile_image_url VARCHAR(255) NULL, | ||
| `role` ENUM ('ADMIN', 'USER') NOT NULL, | ||
| terms_agreed BIT(1) NOT NULL, | ||
| apple_refresh_token VARCHAR(1024) NULL, | ||
| CONSTRAINT pk_users PRIMARY KEY (id) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick (assertive)
BOOLEAN 매핑은 BIT(1) 대신 TINYINT(1) 사용을 고려해 주세요.
MySQL에서 BIT(1)는 드라이버/ORM에 따라 바이트 배열로 매핑되는 이슈가 있습니다. Hibernate/MySQL 조합에서는 TINYINT(1) → boolean이 보다 안전합니다.
다음 변경을 권장합니다:
- terms_agreed BIT(1) NOT NULL,
+ terms_agreed TINYINT(1) NOT NULL,사용자 엔티티 매핑이 @column(columnDefinition = "BIT(1)") 등으로 고정되어 있지 않은지 확인 부탁드립니다. 필요 시 매핑도 함께 정렬해 드리겠습니다.
🤖 Prompt for AI Agents
In infra/src/main/resources/db/migration/mysql/V1__init.sql around lines 7 to
15, change the terms_agreed column definition from BIT(1) to TINYINT(1) to avoid
JDBC/ORM mapping issues where BIT can map to a byte array; update the DDL to use
TINYINT(1) NOT NULL and adjust any default values as needed, then verify the
Java entity mapping does not forcibly define columnDefinition = "BIT(1)" (if it
does, change it to match TINYINT(1) or remove the columnDefinition so Hibernate
maps boolean correctly).
| CREATE TABLE user_books | ||
| ( | ||
| id VARCHAR(36) NOT NULL, | ||
| created_at datetime(6) NOT NULL, | ||
| updated_at datetime(6) NOT NULL, | ||
| deleted_at datetime(6) NULL, | ||
| user_id VARCHAR(36) NOT NULL, | ||
| book_id VARCHAR(36) NOT NULL, | ||
| book_isbn13 VARCHAR(255) NOT NULL, | ||
| cover_image_url VARCHAR(2048) NOT NULL, | ||
| publisher VARCHAR(300) NOT NULL, | ||
| title VARCHAR(255) NOT NULL, | ||
| author VARCHAR(255) NULL, | ||
| `status` ENUM ('BEFORE_READING', 'BEFORE_REGISTRATION', 'COMPLETED', 'READING') NOT NULL, | ||
| reading_record_count INT NOT NULL, | ||
| CONSTRAINT pk_user_books PRIMARY KEY (id) | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
user_books에 대한 추가 인덱스 재검토
현재 title, (user_id, title) 인덱스는 존재하나, book_id 단일 인덱스가 없어 책 단위 조회/집계 시 비효율이 발생할 수 있습니다. (user_id, title)로 user_id 검색은 가능하지만, book_id 기준 접근 경로가 없습니다.
아래 인덱스를 추가해 주세요:
CREATE INDEX idx_user_books_title ON user_books (title);
CREATE INDEX idx_user_books_user_id_title ON user_books (user_id, title);
+CREATE INDEX idx_user_books_book_id ON user_books (book_id);🤖 Prompt for AI Agents
infra/src/main/resources/db/migration/mysql/V1__init.sql around lines 34-50: the
table user_books lacks a single-column index on book_id which hurts book-level
lookups/aggregations; add a non-unique index on book_id (e.g.,
idx_user_books_book_id) to the DDL (placed before the closing ); then
regenerate/apply the migration so the new index is created in the database.
| book_isbn13 VARCHAR(255) NOT NULL, | ||
| cover_image_url VARCHAR(2048) NOT NULL, | ||
| publisher VARCHAR(300) NOT NULL, | ||
| title VARCHAR(255) NOT NULL, | ||
| author VARCHAR(255) NULL, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
user_books.book_isbn13 길이 255는 도메인 제약(13자리)과 불일치합니다.
isbn13은 13자리로 정의되어 있어야 합니다. 255는 데이터 정합성 저하와 인덱스/스토리지 낭비를 유발합니다.
다음과 같이 수정해 주세요:
- book_isbn13 VARCHAR(255) NOT NULL,
+ book_isbn13 VARCHAR(13) NOT NULL,📝 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.
| book_isbn13 VARCHAR(255) NOT NULL, | |
| cover_image_url VARCHAR(2048) NOT NULL, | |
| publisher VARCHAR(300) NOT NULL, | |
| title VARCHAR(255) NOT NULL, | |
| author VARCHAR(255) NULL, | |
| book_isbn13 VARCHAR(13) NOT NULL, | |
| cover_image_url VARCHAR(2048) NOT NULL, | |
| publisher VARCHAR(300) NOT NULL, | |
| title VARCHAR(255) NOT NULL, | |
| author VARCHAR(255) NULL, |
🤖 Prompt for AI Agents
infra/src/main/resources/db/migration/mysql/V1__init.sql around lines 42 to 46:
user_books.book_isbn13 is declared VARCHAR(255) but the domain requires exactly
13 characters; change the column to CHAR(13) NOT NULL (or at minimum VARCHAR(13)
NOT NULL) to enforce the ISBN-13 length, reduce storage/index bloat, and
preserve data integrity.
| CREATE TABLE reading_records | ||
| ( | ||
| id VARCHAR(36) NOT NULL, | ||
| created_at datetime(6) NOT NULL, | ||
| updated_at datetime(6) NOT NULL, | ||
| deleted_at datetime(6) NULL, | ||
| user_book_id VARCHAR(36) NOT NULL, | ||
| page_number INT NOT NULL, | ||
| quote VARCHAR(1000) NOT NULL, | ||
| review VARCHAR(1000) NOT NULL, | ||
| CONSTRAINT pk_reading_records PRIMARY KEY (id) | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
조인 키에 대한 인덱스가 없습니다. 조회 성능 저하 가능성이 높습니다.
- reading_records.user_book_id
- reading_record_tags.reading_record_id, reading_record_tags.tag_id
해당 컬럼들에 인덱스를 추가해 조인/필터 성능을 확보하세요. 또한 동일 (reading_record_id, tag_id) 중복 방지를 위해 유니크 인덱스도 권장합니다.
아래 인덱스를 추가하세요(파일 하단 인덱스 섹션에 이어서):
CREATE INDEX idx_user_books_title ON user_books (title);
CREATE INDEX idx_user_books_user_id_title ON user_books (user_id, title);
+
+-- Additional indexes for join performance and integrity
+CREATE INDEX idx_reading_records_user_book_id ON reading_records (user_book_id);
+CREATE INDEX idx_rrt_reading_record_id ON reading_record_tags (reading_record_id);
+CREATE INDEX idx_rrt_tag_id ON reading_record_tags (tag_id);
+CREATE UNIQUE INDEX uc_rrt_reading_record_id_tag_id ON reading_record_tags (reading_record_id, tag_id);🤖 Prompt for AI Agents
In infra/src/main/resources/db/migration/mysql/V1__init.sql around lines 52-63,
add indexes for join/filter performance: create a non-unique index on
reading_records.user_book_id, create non-unique indexes on
reading_record_tags.reading_record_id and reading_record_tags.tag_id, and add a
unique composite index on reading_record_tags(reading_record_id, tag_id) to
prevent duplicates; append these CREATE INDEX / CREATE UNIQUE INDEX statements
to the file's index section at the bottom so migrations include them.
|
고생하셨습니다! Versioning을 어떤 기준으로 가지고 메이저버전,마이너버전,패치버전을 가져갈 것인지도 의논이 필요해보입니다~ |
| @@ -0,0 +1,94 @@ | |||
| CREATE TABLE users | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
init하는 코드 제일위에
ex)
drop table if exists profile;
drop table if exists comment;
drop table if exists board;
drop table if exists member;
위 예시처럼 테이블 존재한다면 삭제하고 지우는방향도 좋아보입니다!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Dev의 경우 이미 스키마와 각 내부에 테이블이 있어서 drop을 하고 create를 하는 방식도 좋은 방식인 것 같습니다!
다만 찾아보니, 기존 프로젝트에 Flyway를 도입하는 경우에는 baseline-on-migrate: true 와 baseline-version: 1 설정을 통해 해결할 수 있더라고요!!
이 설정을 적용하면 현재 DB 상태가 기준선(baseline)으로 기록되어 V1 버전부터 관리가 시작되므로, 기존 데이터베이스 구조를 그대로 유지하면서도 이후 마이그레이션을 안전하게 적용할 수 있어서 적용했습니다.
아래는 클로드 답변 첨부할게요! (더 잘 설명해주는거 같아서 ㅎㅎ)
Flyway baseline 개념 정리
일반적인 Flyway 동작 (baseline-on-migrate: false)
빈 DB → V1__init.sql 실행 → V2__xxx.sql 실행 → ...
- 빈 DB에서 시작하는 것이 전제
- 기존 테이블이 있으면 에러 발생
baseline-on-migrate: true의 등장 배경
- 문제 상황: 이미 운영 중인 DB에 Flyway를 도입하고 싶을 때
기존 운영 DB (테이블들이 이미 존재)
↓
Flyway 도입하고 싶음
↓
V1__init.sql을 실행하면?
→ "테이블이 이미 있습니다" 에러!baseline-on-migrate: true 해결책
flyway:
baseline-on-migrate: true
baseline-version: 1동작 과정:
- Flyway가 실행됨
- flyway_schema_history 테이블이 없음을 확인
- "아, 기존 DB구나! V1은 이미 적용된 것으로 간주하자"
- flyway_schema_history에 V1을 실행됨으로 기록 (실제 실행 없이!)
- V2부터 실행 시작
minwoo1999
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
고생하셨습니다 리뷰 몇가지 남겼습니다 ~~!
| @@ -0,0 +1,7 @@ | |||
| INSERT INTO tags (id, name, created_at, updated_at) | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
태그들을 미리 insert 해주셨는데, 이부분을 미리 넣어주신 이유가 궁금합니다
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이 4개의 태그는 서비스 비즈니스(감정 태그 선택)에서 고정적으로 사용되는 값이므로, 초기 seed 데이터로 미리 정의하여 삽입하는 방향이 적합해 보여서 도입했습니다!
사실 성능상 미미한 부분이지만, 미리 정의된 태그를 넣어준다면 서비스 레이어의 findOrCreate 로직에서 불필요한 Create 없이 바로 find만 해서 원하는 태그를 찾아가는 데에도 도움이 된다고 생각했습니다.
추가적으로 해당 태그 네이밍은 추후 비즈니스 요구사항에 따라 변경될 수 있는 부분이라고 생각했는데요! 그래서 V(Versioned)가 아닌 R(Repeatable) 스크립트로 관리함으로써, 나중에 태그 목록이 변경되더라도 이 스크립트 파일 하나만 수정하면 모든 데이터베이스에 일관되게 최신 상태를 반영할 수 있도록 유연성을 확보했습니다.
결론적으로, 이 방식은 데이터의 일관성을 보장하고, 성능을 최적화하며, 미래의 변경사항에 유연하게 대처할 수 있는 가장 좋은 방법이라고 판단해서 적용해보았습니다 ㅎㅎ
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (2)
infra/src/main/kotlin/org/yapp/infra/userbook/entity/UserBookEntity.kt(1 hunks)infra/src/main/resources/db/migration/mysql/V20250820_001__ALTER_User_books_Book_isbn13_To_Varchar13.sql(1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build-validation
🔇 Additional comments (1)
infra/src/main/kotlin/org/yapp/infra/userbook/entity/UserBookEntity.kt (1)
37-39: 엔터티 컬럼 길이(13) 명시로 Flyway 변경과의 정합성 확보: LGTMDB 마이그레이션(VARCHAR/CHAR 13)과 JPA 매핑 길이가 일치합니다. 도메인 타입(UserBook.BookIsbn13)과도 정합성이 맞습니다.
| @Column(name = "book_isbn13", nullable = false, length = 13) | ||
| val bookIsbn13: String, | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick (assertive)
Bean Validation으로 조기 검증 추가(선택)
DB 제약 전 단계에서 잘못된 값(길이/숫자 아님)을 차단하려면 Bean Validation을 붙이는 것을 권장합니다. 도메인에서 이미 보장된다면 선택 사항입니다.
- @Column(name = "book_isbn13", nullable = false, length = 13)
+ @field:jakarta.validation.constraints.Size(min = 13, max = 13)
+ @field:jakarta.validation.constraints.Pattern(regexp = "\\d{13}")
+ @Column(name = "book_isbn13", nullable = false, length = 13)
val bookIsbn13: String,추가로 import가 필요합니다(파일 상단):
import jakarta.validation.constraints.Pattern
import jakarta.validation.constraints.Size🤖 Prompt for AI Agents
In infra/src/main/kotlin/org/yapp/infra/userbook/entity/UserBookEntity.kt around
lines 37 to 39, add Bean Validation annotations to the bookIsbn13 property to
validate length and numeric-only characters before hitting the DB: annotate the
property with @Size(min = 13, max = 13) and @Pattern(regexp = "\\d{13}") (or
equivalent numeric regex) and add the required imports at the top of the file:
jakarta.validation.constraints.Size and jakarta.validation.constraints.Pattern.
🧹 Nitpick (assertive)
컬럼 타입 고정(선택) — DB를 CHAR(13)로 채택 시 매핑 동기화
만약 마이그레이션을 CHAR(13)로 채택한다면, 스키마 드리프트를 피하기 위해 명시적으로 columnDefinition을 고정할 수 있습니다. 현재 VARCHAR(13)를 유지한다면 이 제안은 생략하세요.
- @Column(name = "book_isbn13", nullable = false, length = 13)
+ @Column(
+ name = "book_isbn13",
+ nullable = false,
+ length = 13,
+ columnDefinition = "char(13)"
+ )
val bookIsbn13: String,🤖 Prompt for AI Agents
In infra/src/main/kotlin/org/yapp/infra/userbook/entity/UserBookEntity.kt around
lines 37 to 39, the column mapping for bookIsbn13 currently defaults to
VARCHAR(13); if your DB migration uses CHAR(13) you should explicitly fix the
JPA mapping by adding columnDefinition = "CHAR(13)" to the @Column to keep
schema in sync (otherwise leave as-is), and ensure length/nullable remain
consistent with the DB migration.
💡 Verification agent
🧩 Analysis chain
레포 전반의 isbn13 길이 정합성 점검 제안
동일한 속성명이 다른 엔터티/DTO/쿼리에서 상이한 길이로 선언되어 있지 않은지 확인 부탁드립니다. 아래 스크립트로 빠르게 점검할 수 있습니다.
🏁 Script executed:
#!/bin/bash
# 레포 전반에서 isbn13 사용처와 길이/제약 표기를 탐색
rg -n -C2 -S -g '!*build/*' -g '!*out/*' -g '!*node_modules/*' '\bisbn13\b|book_isbn13|@Column\([^)]*length\s*=\s*13'Length of output: 38547
초기 마이그레이션 book_isbn13 길이 불일치 확인 및 수정 제안
엔티티(@column)와 DTO, 요청 검증은 모두 길이 13으로 일관되나, DB 마이그레이션 스크립트 간 정의가 다릅니다.
- infra/src/main/resources/db/migration/mysql/V1__init.sql:42
book_isbn13 VARCHAR(255) NOT NULL - infra/src/main/resources/db/migration/mysql/V20250820_001__ALTER_User_books_Book_isbn13_To_Varchar13.sql:2
MODIFY COLUMN book_isbn13 VARCHAR(13) NOT NULL;
신규 설치 시에는 V1 → V20250820 순으로 적용되어 최종 VARCHAR(13) 이지만, 스크립트 간 일관성을 위해 V1 스크립트에서도 book_isbn13을 VARCHAR(13)으로 수정하거나 해당 이력을 문서화해두시길 권장드립니다.
🤖 Prompt for AI Agents
infra/src/main/kotlin/org/yapp/infra/userbook/entity/UserBookEntity.kt lines
37-39: the entity defines bookIsbn13 length 13 but the initial migration
V1__init.sql sets book_isbn13 VARCHAR(255), causing inconsistency; update
infra/src/main/resources/db/migration/mysql/V1__init.sql to define book_isbn13
VARCHAR(13) NOT NULL to match the entity and later ALTER script, or
alternatively add a clear comment/doc entry in the migration folder explaining
that V1 used VARCHAR(255) and was intentionally reduced to VARCHAR(13) by
V20250820_001 so maintainers understand the history.
| ALTER TABLE user_books | ||
| MODIFY COLUMN book_isbn13 VARCHAR(13) NOT NULL; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
❓ Verification inconclusive
ISBN-13 무결성 보장을 위한 사전 정규화 + 제약 추가 제안
길이를 13으로 축소하는 ALTER는 기존 데이터에 하이픈/공백/문자 등이 포함되어 있거나 13자를 초과하는 값이 존재할 경우, 환경의 SQL_MODE(STRICT) 설정에 따라 실패하거나 침묵 상태로 잘릴 수 있습니다. 마이그레이션 안전성을 높이기 위해 다음을 권장합니다:
- 사전 정규화(비숫자 제거) UPDATE
- CHAR(13)로 고정 길이 저장 또는 현행 VARCHAR(13) 유지 시 CHECK 제약으로 숫자 13자리 강제
- MySQL 8.0 사용 여부 확인(CHK/REGEXP_REPLACE 지원)
아래와 같이 보완하면 재실행 안전성 및 데이터 무결성이 좋아집니다.
-ALTER TABLE user_books
- MODIFY COLUMN book_isbn13 VARCHAR(13) NOT NULL;
+-- 1) ISBN 값 정규화(하이픈/공백 등 비숫자 제거)
+-- MySQL 8.0+: REGEXP_REPLACE 사용
+UPDATE user_books
+SET book_isbn13 = REGEXP_REPLACE(book_isbn13, '[^0-9]', '')
+WHERE book_isbn13 REGEXP '[^0-9]';
+
+-- (MySQL 5.7 호환 대안)
+-- UPDATE user_books
+-- SET book_isbn13 = REPLACE(REPLACE(REPLACE(book_isbn13, '-', ''), ' ', ''), '.', '')
+-- WHERE book_isbn13 REGEXP '[^0-9]';
+
+-- 2) 스키마 제약 강화
+-- (옵션 A) 고정 길이 코드이므로 CHAR(13) 선택
+ALTER TABLE user_books
+ MODIFY COLUMN book_isbn13 CHAR(13) NOT NULL,
+ ADD CONSTRAINT chk_user_books_isbn13 CHECK (book_isbn13 REGEXP '^[0-9]{13}$');
+
+-- (옵션 B) VARCHAR(13) 유지 시에도 CHECK는 동일하게 적용 가능
+-- ALTER TABLE user_books
+-- MODIFY COLUMN book_isbn13 VARCHAR(13) NOT NULL,
+-- ADD CONSTRAINT chk_user_books_isbn13 CHECK (book_isbn13 REGEXP '^[0-9]{13}$');검증 포인트:
- 운영/스테이징 DB에서 아래 쿼리로 위반 데이터 존재 여부를 선확인해주세요.
- SELECT id, book_isbn13 FROM user_books WHERE book_isbn13 REGEXP '[^0-9]' OR CHAR_LENGTH(book_isbn13) <> 13;
- MySQL 8.0 미만이라면 CHECK는 파싱만 되고 미적용됩니다. 해당 경우 애플리케이션/도메인 레이어 검증을 보강해야 합니다.
- 대용량 테이블인 경우 ALTER로 인한 잠금/재빌드 영향 검토가 필요합니다(스키마 변경 시간 산정, 배포 윈도우 고려).
ISBN-13 컬럼 변경 전 데이터 정규화 및 체크 제약 강화 필수
user_books.book_isbn13을 VARCHAR(13)으로 축소하기 전후에 데이터 무결성과 마이그레이션 안전성을 보장하기 위해 아래 보완이 필요합니다.
- 파일: infra/src/main/resources/db/migration/mysql/V20250820_001__ALTER_User_books_Book_isbn13_To_Varchar13.sql (1–2행)
- 현재 내용:
ALTER TABLE user_books MODIFY COLUMN book_isbn13 VARCHAR(13) NOT NULL;
제안하는 보완 스크립트:
-ALTER TABLE user_books
- MODIFY COLUMN book_isbn13 VARCHAR(13) NOT NULL;
+-- 1) ISBN-13 사전 정규화(하이픈/공백/문자 제거)
+UPDATE user_books
+SET book_isbn13 = REGEXP_REPLACE(book_isbn13, '[^0-9]', '')
+WHERE book_isbn13 REGEXP '[^0-9]';
+
+-- MySQL 5.7 호환 대안
+-- UPDATE user_books
+-- SET book_isbn13 = REPLACE(REPLACE(REPLACE(book_isbn13, '-', ''), ' ', ''), '.', '')
+-- WHERE book_isbn13 REGEXP '[^0-9]';
+
+-- 2) 스키마 제약 강화
+ALTER TABLE user_books
+ MODIFY COLUMN book_isbn13 CHAR(13) NOT NULL,
+ ADD CONSTRAINT chk_user_books_isbn13
+ CHECK (book_isbn13 REGEXP '^[0-9]{13}$');
+
+-- (옵션) VARCHAR(13) 유지 시에도 동일한 CHECK 제약 적용 가능
+-- ALTER TABLE user_books
+-- MODIFY COLUMN book_isbn13 VARCHAR(13) NOT NULL,
+-- ADD CONSTRAINT chk_user_books_isbn13
+-- CHECK (book_isbn13 REGEXP '^[0-9]{13}$');검증 포인트:
- 운영/스테이징 DB에서
로 위반 데이터 존재 여부 확인
SELECT id, book_isbn13 FROM user_books WHERE book_isbn13 REGEXP '[^0-9]' OR CHAR_LENGTH(book_isbn13) <> 13;
- MySQL 8.0 미만 환경에서는 CHECK 제약이 파싱만 되고 적용되지 않으므로 애플리케이션 레이어 검증 보강 필요
- 대용량 테이블의 경우 ALTER 잠금 및 리빌드 시간 산정 후 배포 윈도우 계획 필요



🔗 관련 이슈
📘 작업 유형
📙 작업 내역
Flyway 설정 및 환경별 구성
데이터베이스 스키마 마이그레이션 파일 생성
시드 데이터 관리 체계 구축
Database Schema Governance Strategy 문서화
🧪 테스트 내역
🎨 스크린샷 또는 시연 영상 (선택)
✅ PR 체크리스트
💬 추가 설명 or 리뷰 포인트 (선택)
Summary by CodeRabbit
새 기능
개선
문서