[STK5-01] 공통 DB 스키마 (accounts·orders·holdings·trade_history DDL)
작업 내용 (설계 의도)
변경 사항
Phase 5에서 사용할 4개 테이블을 Flyway 마이그레이션으로 신규 생성한다. STK5-02·STK5-03이 이 스키마를 기반으로 Entity·Repository를 구현하므로, Wave 1에서 단독 완료해야 한다.
각 테이블 설계 의도:
toss_tokens: access_token + expires_at 영속화. 앱 재시작 후 토큰 재발급 없이 재사용. 행은 항상 1건(upsert).accounts: 토스 계좌 목록 캐시.selected TINYINT(1)으로 활성 계좌 1개 추적.orders: 주문 이력.toss_order_id UK로 Toss와 MySQL 간 멱등성 보장.holdings: 보유종목 스냅샷.(account_number, symbol)복합 UK로 upsert.trade_history: 거래내역 스냅샷.toss_transaction_id UK로 중복 적재 방지.
다이어그램
처리 흐름 (Flyway 마이그레이션 적용 순서)
sequenceDiagram participant F as Flyway participant DB as MySQL(3308) F->>DB: V{ts}__create_accounts.sql DB-->>F: OK F->>DB: V{ts}__create_orders.sql DB-->>F: OK F->>DB: V{ts}__create_holdings.sql DB-->>F: OK F->>DB: V{ts}__create_trade_history.sql DB-->>F: OK Note over DB: 4개 테이블 + UK/인덱스 생성 완료
클래스 의존
flowchart LR subgraph Migration["Flyway Migration"] M1[accounts DDL] M2[orders DDL] M3[holdings DDL] M4[trade_history DDL] end subgraph Tables["MySQL Tables"] A[(accounts)] O[(orders)] H[(holdings)] T[(trade_history)] end M1 -->|creates| A M2 -->|creates| O M3 -->|creates| H M4 -->|creates| T O -->|account_number ref| A H -->|account_number ref| A T -->|account_number ref| A
ERD
erDiagram ACCOUNTS { varchar account_number PK varchar account_name varchar account_type decimal cash_balance tinyint selected datetime refreshed_at datetime created_at } ORDERS { bigint id PK varchar toss_order_id UK varchar account_number varchar symbol varchar order_type varchar price_type int quantity decimal price varchar status datetime ordered_at datetime corrected_at datetime cancelled_at } HOLDINGS { bigint id PK varchar account_number varchar symbol int quantity decimal average_price datetime refreshed_at } TRADE_HISTORY { bigint id PK varchar toss_transaction_id UK varchar account_number varchar symbol varchar trade_type int quantity decimal price datetime traded_at } ACCOUNTS ||--o{ ORDERS : "account_number ref" ACCOUNTS ||--o{ HOLDINGS : "account_number ref" ACCOUNTS ||--o{ TRADE_HISTORY : "account_number ref"
DDL 참고
CREATE TABLE toss_tokens (
id BIGINT NOT NULL AUTO_INCREMENT COMMENT '내부 ID',
access_token TEXT NOT NULL COMMENT '토스 access token (JWT)',
expires_at DATETIME(6) NOT NULL COMMENT '토큰 만료 시각',
issued_at DATETIME(6) NOT NULL COMMENT '발급 시각',
PRIMARY KEY (id)
) COMMENT = '토스 API 토큰 영속화 (항상 최신 1건)';
CREATE TABLE accounts (
account_number VARCHAR(20) NOT NULL COMMENT '토스 계좌번호',
account_name VARCHAR(100) NOT NULL COMMENT '계좌명',
account_type VARCHAR(20) NOT NULL COMMENT '계좌 유형 (STOCK/ISA/CMA)',
cash_balance DECIMAL(18, 2) NOT NULL DEFAULT 0 COMMENT '예수금',
selected TINYINT(1) NOT NULL DEFAULT 0 COMMENT '활성 계좌 여부 (1개만 1)',
refreshed_at DATETIME(6) NOT NULL COMMENT '마지막 동기화 시각',
created_at DATETIME(6) NOT NULL COMMENT '최초 등록 시각',
PRIMARY KEY (account_number)
) COMMENT = '토스 계좌 캐시';
CREATE TABLE orders (
id BIGINT NOT NULL AUTO_INCREMENT COMMENT '내부 주문 ID',
toss_order_id VARCHAR(50) NOT NULL COMMENT '토스 발급 주문 ID',
account_number VARCHAR(20) NOT NULL COMMENT '주문 계좌번호',
symbol VARCHAR(20) NOT NULL COMMENT '종목 코드',
order_type VARCHAR(10) NOT NULL COMMENT '주문 방향 (BUY/SELL)',
price_type VARCHAR(10) NOT NULL COMMENT '가격 유형 (LIMIT/MARKET)',
quantity INT NOT NULL COMMENT '주문 수량',
price DECIMAL(18, 2) NOT NULL COMMENT '주문 가격',
status VARCHAR(20) NOT NULL COMMENT '주문 상태 (PENDING/COMPLETED/CORRECTED/CANCELLED)',
ordered_at DATETIME(6) NOT NULL COMMENT '주문 접수 시각',
corrected_at DATETIME(6) NULL COMMENT '정정 시각',
cancelled_at DATETIME(6) NULL COMMENT '취소 시각',
PRIMARY KEY (id),
UNIQUE KEY uk_toss_order_id (toss_order_id),
INDEX idx_account_number (account_number),
INDEX idx_status (status)
) COMMENT = '주문 이력';
CREATE TABLE holdings (
id BIGINT NOT NULL AUTO_INCREMENT COMMENT '내부 ID',
account_number VARCHAR(20) NOT NULL COMMENT '계좌번호',
symbol VARCHAR(20) NOT NULL COMMENT '종목 코드',
quantity INT NOT NULL COMMENT '보유 수량',
average_price DECIMAL(18, 2) NOT NULL COMMENT '평균 매수가',
refreshed_at DATETIME(6) NOT NULL COMMENT '마지막 동기화 시각',
PRIMARY KEY (id),
UNIQUE KEY uk_account_symbol (account_number, symbol)
) COMMENT = '보유 종목 스냅샷';
CREATE TABLE trade_history (
id BIGINT NOT NULL AUTO_INCREMENT COMMENT '내부 ID',
toss_transaction_id VARCHAR(50) NOT NULL COMMENT '토스 거래 ID',
account_number VARCHAR(20) NOT NULL COMMENT '계좌번호',
symbol VARCHAR(20) NOT NULL COMMENT '종목 코드',
trade_type VARCHAR(10) NOT NULL COMMENT '거래 유형 (BUY/SELL)',
quantity INT NOT NULL COMMENT '거래 수량',
price DECIMAL(18, 2) NOT NULL COMMENT '체결가',
traded_at DATETIME(6) NOT NULL COMMENT '체결 시각',
PRIMARY KEY (id),
UNIQUE KEY uk_toss_transaction_id (toss_transaction_id),
INDEX idx_account_traded (account_number, traded_at)
) COMMENT = '거래 내역 스냅샷';롤백: DROP TABLE trade_history; DROP TABLE holdings; DROP TABLE orders; DROP TABLE accounts;
테스트 케이스
- Flyway 마이그레이션 4개 스크립트가 로컬 MySQL 8.0에서 오류 없이 순서대로 실행된다.
orders테이블에 동일toss_order_id2건을 INSERT하면 Duplicate Key 에러가 발생한다.holdings테이블에 동일(account_number, symbol)조합으로 UPSERT 2회 시 행이 1건으로 유지되고refreshed_at이 최신값으로 갱신된다.trade_history테이블에 동일toss_transaction_id2건을 INSERT하면 Duplicate Key 에러가 발생한다.accounts.selected = 1인 행이 0개인 상태에서도 스키마 오류 없이 조회가 가능하다 (DB 레벨 강제 아님, 애플리케이션 제어).