1. 마스터 설정 (master.xml)
데이터베이스별 프로퍼티 정의
H2 Database (테스트/개발용)
1
2
3
4
5
6
| <property name="now" value="now()" dbms="h2"/>
<property name="floatType" value="float4" dbms="h2"/>
<property name="uuidType" value="uuid" dbms="h2"/>
<property name="datetimeType" value="datetime" dbms="h2"/>
<property name="clobType" value="longvarchar" dbms="h2"/>
<property name="blobType" value="blob" dbms="h2"/>
|
PostgreSQL (운영용)
1
2
3
4
5
6
| <property name="now" value="current_timestamp" dbms="postgresql"/>
<property name="floatType" value="float4" dbms="postgresql"/>
<property name="clobType" value="longvarchar" dbms="postgresql"/>
<property name="blobType" value="bytea" dbms="postgresql"/>
<property name="uuidType" value="uuid" dbms="postgresql"/>
<property name="datetimeType" value="timestamp" dbms="postgresql"/>
|
프로퍼티 정의 표
| 프로퍼티 | H2 | PostgreSQL | 용도 |
|---|
now | now() | current_timestamp | 현재 시간 함수 |
floatType | float4 | float4 | 실수형 타입 |
uuidType | uuid | uuid | UUID 타입 |
datetimeType | datetime | timestamp | 날짜-시간 타입 |
clobType | longvarchar | longvarchar | 긴 텍스트 타입 |
blobType | blob | bytea | 바이너리 타입 |
2. ChangeSet 네이밍 규칙
파일명 형식
1
| YYYYMMDDhhmmss_added_entity_EntityName.xml
|
예시:
20240819000001_added_entity_Calendar.xml20230707000000_added_entity_Protocol.xml
ChangeSet ID 형식
스키마 생성
1
| <changeSet id="20240819000001-1-schema" author="zero">
|
데이터 로드
1
| <changeSet id="20240819000001-2-data" author="zero">
|
Context 사용 (선택적)
1
| <changeSet id="20240819000001-2-data" author="zero" context="faker">
|
3. 테이블 생성 패턴
기본 구조
1
2
3
4
5
| <changeSet id="YYYYMMDD000001-1-schema" author="zero">
<createTable tableName="ph_table_name">
<!-- 컬럼 정의 -->
</createTable>
</changeSet>
|
표준 컬럼 타입
숫자형
1
2
3
4
5
6
| <column name="id" type="bigint">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="order_no" type="integer" defaultValueNumeric="0">
<constraints nullable="false" />
</column>
|
문자열
1
2
3
4
5
6
| <column name="title" type="varchar(50)">
<constraints nullable="false" />
</column>
<column name="description" type="varchar(255)">
<constraints nullable="true" />
</column>
|
Boolean
1
2
3
| <column name="activated" type="boolean" defaultValueBoolean="true">
<constraints nullable="false" />
</column>
|
Timestamp (중요!)
1
2
3
4
| <!-- createTable에서는 timestamp 직접 사용 -->
<column name="created_date" type="timestamp" />
<column name="last_modified_by" type="varchar(50)" />
<column name="last_modified_date" type="timestamp" />
|
감사(Audit) 컬럼 패턴
1
2
3
4
5
6
| <column name="created_by" type="varchar(50)">
<constraints nullable="false"/>
</column>
<column name="created_date" type="timestamp"/>
<column name="last_modified_by" type="varchar(50)"/>
<column name="last_modified_date" type="timestamp"/>
|
제약조건 패턴
Primary Key
1
| <constraints primaryKey="true" nullable="false"/>
|
Unique
1
| <constraints nullable="false" unique="true"/>
|
Foreign Key (외래키는 별도 ChangeSet 권장)
1
2
3
4
5
6
| <addForeignKeyConstraint
baseTableName="ph_field"
baseColumnNames="collection_id"
constraintName="fk_ph_field_ph_collection"
referencedTableName="ph_collection"
referencedColumnNames="id"/>
|
4. 데이터 로드 패턴 (중요!)
기본 구조
1
2
3
4
5
6
7
8
9
10
| <changeSet id="YYYYMMDD000001-2-data" author="zero">
<loadData
file="config/liquibase/fake-data/table_name.csv"
separator=";"
tableName="ph_table_name"
usePreparedStatements="true">
<!-- 컬럼 타입 정의 -->
</loadData>
<!-- timestamp 컬럼이 있으면 dropDefaultValue 필수 -->
</changeSet>
|
loadData 컬럼 타입 매핑
CSV → loadData 타입 매핑 규칙
| CSV 데이터 | createTable 타입 | loadData 타입 | 비고 |
|---|
1, 123 | bigint, integer | numeric | 숫자 |
"text" | varchar(N) | string | 문자열 |
true, false | boolean | boolean | 불린 |
2024-01-01T10:00:00 | timestamp | date ⚠️ | 중요! |
⚠️ Timestamp 처리 (핵심 패턴)
잘못된 방법:
1
2
3
4
| <!-- ❌ 에러 발생! -->
<loadData ...>
<column name="start_date" type="timestamp" />
</loadData>
|
올바른 방법:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| <!-- ✅ 올바른 방법 -->
<loadData
file="config/liquibase/fake-data/calendar.csv"
separator=";"
tableName="ph_calendar"
usePreparedStatements="true">
<column name="id" type="numeric" />
<column name="title" type="string" />
<column name="start_date" type="date" /> <!-- date 사용! -->
<column name="end_date" type="date" /> <!-- date 사용! -->
<column name="created_by" type="string" />
</loadData>
<!-- dropDefaultValue 필수! -->
<dropDefaultValue tableName="ph_calendar" columnName="start_date"
columnDataType="${datetimeType}"/>
<dropDefaultValue tableName="ph_calendar" columnName="end_date"
columnDataType="${datetimeType}"/>
|
완전한 예시 (Calendar 엔티티)
CSV 파일 (calendar.csv)
id;title;description;all_day;start_date;end_date;color;text_color;created_by
1;Event 1;Description 1;false;2024-01-01T10:00:00;2024-01-01T12:00:00;#FF0000;#FFFFFF;system
ChangeSet
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| <changeSet id="20240819000001-2-data" author="zero">
<loadData
file="config/liquibase/fake-data/calendar.csv"
separator=";"
tableName="ph_calendar"
usePreparedStatements="true">
<column name="id" type="numeric" />
<column name="title" type="string" />
<column name="description" type="string" />
<column name="all_day" type="boolean" />
<column name="start_date" type="date" />
<column name="end_date" type="date" />
<column name="color" type="string" />
<column name="text_color" type="string" />
<column name="created_by" type="string" />
</loadData>
<dropDefaultValue tableName="ph_calendar" columnName="start_date"
columnDataType="${datetimeType}"/>
<dropDefaultValue tableName="ph_calendar" columnName="end_date"
columnDataType="${datetimeType}"/>
</changeSet>
|
5. dropDefaultValue 사용법
용도
- CSV 데이터 정확성 보장: timestamp 컬럼의 기본값 제거
- 타입 변환 문제 해결: Liquibase-PostgreSQL 간 타입 정보 동기화
문법
1
2
3
4
| <dropDefaultValue
tableName="테이블명"
columnName="컬럼명"
columnDataType="${datetimeType}"/>
|
사용 규칙
- timestamp 타입 컬럼마다 필수
- loadData 직후에 배치
columnDataType에는 ${datetimeType} 변수 사용 (DB 독립성)
예시
1
2
3
4
5
6
7
8
| <loadData ...>
<column name="created_date" type="date" />
<column name="last_modified_date" type="date" />
</loadData>
<dropDefaultValue tableName="ph_user" columnName="created_date"
columnDataType="${datetimeType}"/>
<dropDefaultValue tableName="ph_user" columnName="last_modified_date"
columnDataType="${datetimeType}"/>
|
6. CSV 파일 형식 규칙
구분자
날짜-시간 형식
# ISO 8601 형식 지원
2024-01-01T10:00:00
2025-07-01 00:00:00
2024-01-01T10:00:00.000Z
문자열 이스케이프
# 따옴표로 감싸기
"text with; semicolon"
"O'Brien"
헤더 필수
id;title;description;created_date
1;"Title 1";"Description";2024-01-01T10:00:00
7. Context 사용
faker context
테스트 데이터 로드용
1
2
3
| <changeSet id="20230705000000-1-data" author="zero" context="faker">
<loadData ... />
</changeSet>
|
실행 시:
1
2
3
4
5
| # faker 데이터 포함
./gradlew bootRun -Pcontexts=faker
# faker 데이터 제외 (운영)
./gradlew bootRun
|
8. 테이블 네이밍 규칙
Prefix
모든 테이블은 ph_ 접두사 사용
1
2
3
4
| ph_calendar
ph_protocol
ph_user
ph_authority
|
Snake Case
단어 구분은 언더스코어(_)
1
2
3
| ph_field_type
ph_user_protocol
ph_formula_attribute
|
9. 체크리스트
새 엔티티 추가 시
- 파일명:
YYYYMMDDhhmmss_added_entity_EntityName.xml - ChangeSet 1: 스키마 생성 (
-1-schema) - ChangeSet 2: 데이터 로드 (
-2-data) - 테이블명:
ph_entity_name - 감사 컬럼 포함 (
created_by, created_date, 등) - CSV 파일 준비 (세미콜론 구분자)
- loadData에서 timestamp → date 타입 사용
- CSV의 모든 컬럼 loadData에 정의
- dropDefaultValue 추가 (timestamp 컬럼마다)
- master.xml에 include 추가
데이터 로드 문제 해결
에러: column is of type timestamp but expression is of type character varying
해결:
- loadData에서
type="timestamp" → type="date" 변경 dropDefaultValue 추가- CSV의 모든 컬럼이 loadData에 정의되었는지 확인
10. 공통 패턴 템플릿
전체 ChangeLog 템플릿
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
| <?xml version="1.0" encoding="utf-8"?>
<databaseChangeLog
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">
<!-- 1. 스키마 생성 -->
<changeSet id="YYYYMMDD000001-1-schema" author="zero">
<createTable tableName="ph_entity_name">
<column name="id" type="bigint">
<constraints primaryKey="true" nullable="false" />
</column>
<column name="title" type="varchar(50)">
<constraints nullable="false" />
</column>
<column name="created_by" type="varchar(50)">
<constraints nullable="false" />
</column>
<column name="created_date" type="timestamp" />
<column name="last_modified_by" type="varchar(50)" />
<column name="last_modified_date" type="timestamp" />
</createTable>
</changeSet>
<!-- 2. 데이터 로드 -->
<changeSet id="YYYYMMDD000001-2-data" author="zero">
<loadData
file="config/liquibase/fake-data/entity_name.csv"
separator=";"
tableName="ph_entity_name"
usePreparedStatements="true">
<column name="id" type="numeric" />
<column name="title" type="string" />
<column name="created_by" type="string" />
<column name="created_date" type="date" />
<column name="last_modified_date" type="date" />
</loadData>
<dropDefaultValue tableName="ph_entity_name" columnName="created_date"
columnDataType="${datetimeType}"/>
<dropDefaultValue tableName="ph_entity_name" columnName="last_modified_date"
columnDataType="${datetimeType}"/>
</changeSet>
</databaseChangeLog>
|
11. 주요 주의사항 ⚠️
1. loadData에서 timestamp 절대 금지
1
2
3
4
5
| <!-- ❌ 절대 이렇게 하지 마세요 -->
<column name="created_date" type="timestamp" />
<!-- ✅ 항상 date를 사용하세요 -->
<column name="created_date" type="date" />
|
2. CSV 컬럼 순서 정확히 일치
CSV 헤더 순서와 loadData의 column 순서가 정확히 일치해야 함
3. dropDefaultValue 필수
timestamp 컬럼마다 반드시 dropDefaultValue 추가
4. 프로퍼티 변수 사용
타입 정의 시 변수 사용으로 DB 독립성 확보
1
2
| columnDataType="${datetimeType}" <!-- 권장 -->
columnDataType="timestamp" <!-- 비권장 -->
|
참고 문서