Flyway를 이용한 데이터베이스 스키마 버전 관리와 마이그레이션 전략을 학습합니다.
Flyway는 데이터베이스 스키마 버전 관리 도구입니다. SQL 스크립트를 버전별로 관리하여 일관된 DB 마이그레이션을 보장합니다.
문제: ddl-auto=update의 한계
해결: Flyway로 스키마 버전 관리
// build.gradle
dependencies {
implementation 'org.flywaydb:flyway-core'
implementation 'org.flywaydb:flyway-mysql' // MySQL 사용 시
}
// application.yml
spring:
flyway:
enabled: true
locations: classpath:db/migration
baseline-on-migrate: true # 기존 DB에 적용 시
baseline-version: 0
out-of-order: false # 순서 강제
validate-on-migrate: true # 마이그레이션 전 검증
jpa:
hibernate:
ddl-auto: validate # Flyway 사용 시 validate 권장V{버전}__{설명}.sql
V1__Create_products_table.sql
V2__Create_orders_table.sql
V3__Add_category_to_products.sql
V1.1__Add_index_to_products.sql
R__{설명}.sql (반복 실행)
R__Create_views.sql
R__Refresh_stored_procedures.sql
주의사항
src/main/resources/
└── db/
└── migration/
├── V1__Create_initial_schema.sql
├── V2__Add_categories.sql
├── V3__Add_version_column.sql
├── V4__Migrate_status_values.sql
└── R__Create_views.sqlFlyway 핵심 원칙
-- V1__Create_initial_schema.sql
-- 초기 스키마 생성
CREATE TABLE members (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
email VARCHAR(100) NOT NULL UNIQUE,
name VARCHAR(50) NOT NULL,
password VARCHAR(255) NOT NULL,
status VARCHAR(20) NOT NULL DEFAULT 'ACTIVE',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE products (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(200) NOT NULL,
sku VARCHAR(50) NOT NULL UNIQUE,
price DECIMAL(10,2) NOT NULL,
stock_quantity INT NOT NULL DEFAULT 0,
status VARCHAR(20) NOT NULL DEFAULT 'DRAFT',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE orders (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
member_id BIGINT NOT NULL,
status VARCHAR(20) NOT NULL DEFAULT 'PENDING',
total_amount DECIMAL(12,2) NOT NULL,
order_date DATETIME NOT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_orders_member FOREIGN KEY (member_id)
REFERENCES members(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 인덱스 생성
CREATE INDEX idx_orders_member ON orders(member_id);
CREATE INDEX idx_orders_status ON orders(status);
CREATE INDEX idx_products_status ON products(status);-- V2__Add_categories.sql
-- 카테고리 테이블 추가
CREATE TABLE categories (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
parent_id BIGINT,
sort_order INT DEFAULT 0,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_category_parent FOREIGN KEY (parent_id)
REFERENCES categories(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- products에 category_id 컬럼 추가
ALTER TABLE products ADD COLUMN category_id BIGINT AFTER status;
ALTER TABLE products ADD CONSTRAINT fk_product_category
FOREIGN KEY (category_id) REFERENCES categories(id);
CREATE INDEX idx_products_category ON products(category_id);-- V3__Add_version_column.sql
-- 낙관적 락을 위한 version 컬럼 추가
ALTER TABLE products ADD COLUMN version BIGINT NOT NULL DEFAULT 0;
ALTER TABLE orders ADD COLUMN version BIGINT NOT NULL DEFAULT 0;
ALTER TABLE members ADD COLUMN version BIGINT NOT NULL DEFAULT 0;-- V4__Migrate_status_values.sql
-- 상태값 변경 (ACTIVE -> PUBLISHED)
-- 트랜잭션으로 안전하게 처리
START TRANSACTION;
UPDATE products SET status = 'PUBLISHED' WHERE status = 'ACTIVE';
UPDATE products SET status = 'INACTIVE' WHERE status = 'DISABLED';
-- 기본 카테고리 데이터 삽입
INSERT INTO categories (name, sort_order) VALUES
('전자제품', 1),
('의류', 2),
('식품', 3),
('도서', 4);
COMMIT;대용량 데이터 마이그레이션 주의
-- 대용량 테이블 업데이트 시 배치 처리
-- V5__Batch_update_products.sql
-- 한 번에 1000건씩 처리
SET @batch_size = 1000;
SET @rows_affected = 1;
WHILE @rows_affected > 0 DO
UPDATE products
SET status = 'PUBLISHED'
WHERE status = 'ACTIVE'
LIMIT @batch_size;
SET @rows_affected = ROW_COUNT();
-- 잠시 대기 (락 경합 방지)
DO SLEEP(0.1);
END WHILE;마이그레이션 스크립트 Best Practices
# application-local.yml (개발)
spring:
flyway:
enabled: true
clean-disabled: false # 개발에서만 clean 허용
validate-on-migrate: true
jpa:
hibernate:
ddl-auto: validate
# application-prod.yml (운영)
spring:
flyway:
enabled: true
clean-disabled: true # 운영에서 clean 절대 금지!
baseline-on-migrate: false
validate-on-migrate: true
out-of-order: false
jpa:
hibernate:
ddl-auto: none # 운영에서는 noneflyway migrate
마이그레이션 실행
flyway info
마이그레이션 상태 확인
flyway validate
스크립트 유효성 검증
flyway repair
실패한 마이그레이션 복구
flyway baseline
기존 DB에 기준점 설정
flyway clean
모든 테이블 삭제 (위험!)
# GitHub Actions 예시
name: Deploy
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Validate Flyway migrations
run: ./gradlew flywayValidate
- name: Run Flyway migrations
run: ./gradlew flywayMigrate
env:
FLYWAY_URL: ${{ secrets.DB_URL }}
FLYWAY_USER: ${{ secrets.DB_USER }}
FLYWAY_PASSWORD: ${{ secrets.DB_PASSWORD }}
- name: Deploy application
run: ./deploy.sh마이그레이션 실패 시 대응
-- 1. flyway_schema_history 테이블 확인
SELECT * FROM flyway_schema_history
ORDER BY installed_rank DESC;
-- 2. 실패한 마이그레이션 확인 (success = 0)
SELECT * FROM flyway_schema_history
WHERE success = 0;
-- 3. repair 명령으로 복구
./gradlew flywayRepair
-- 4. 수동으로 실패 기록 삭제 (최후의 수단)
DELETE FROM flyway_schema_history
WHERE success = 0;Session 1: ORM, 영속성 컨텍스트, 엔티티 생명주기
Session 2: @Entity, @Id, @Column, 타입 매핑
Session 3: 연관관계 매핑, mappedBy, 연관관계의 주인
Session 4: N+1 문제, Fetch Join, @BatchSize
Session 5: Spring Data JPA, Query Methods, Pageable
Session 6: CQRS, Projection, readOnly 최적화
Session 7: @Transactional, 낙관적/비관적 락
Session 8: Flyway DB 마이그레이션
🎉 JPA Workshop 완료!
8개 세션을 통해 JPA의 기초부터 실무 활용까지 학습했습니다. 이제 PHP에서 Spring Boot + JPA로 마이그레이션할 준비가 되었습니다.
# application-local.yml
spring:
flyway:
clean-disabled: false # 로컬에서만 clean 허용
jpa:
hibernate:
ddl-auto: validate
# application-prod.yml
spring:
flyway:
clean-disabled: true
out-of-order: false
jpa:
hibernate:
ddl-auto: none # 운영에서는 절대 auto 금지무중단 배포 시 주의
스키마 변경은 이전 버전 앱과 호환되어야 합니다. 컬럼 삭제/이름 변경은 여러 단계로 나누어 진행합니다.
-- Step 1: 새 컬럼 추가 (V1.1)
ALTER TABLE member ADD COLUMN user_name VARCHAR(100);
UPDATE member SET user_name = name;
-- Step 2: 앱 코드 변경 (배포 2)
-- 앱이 새 컬럼(user_name) 사용하도록 변경
-- Step 3: 기존 컬럼 삭제 (V1.2)
ALTER TABLE member DROP COLUMN name;Checksum 불일치
이미 적용된 파일 수정 시 발생 → flyway_schema_history에서 checksum 업데이트
마이그레이션 실패 후 재시도
실패 기록 삭제 또는 ./gradlew flywayRepair 실행
Flyway Best Practices