Database Migration với Flyway

Table of Contents

Vấn đề — Tại sao ddl-auto=update nguy hiểm trong Production

Từ Bài 5, chúng ta đã sử dụng cấu hình này:

spring.jpa.hibernate.ddl-auto=update

Cấu hình này yêu cầu Hibernate: “So sánh entity class với database và tự động áp dụng thay đổi schema.” Nó cảm giác kỳ diệu trong lúc phát triển, nhưng ẩn chứa vấn đề nghiêm trọng khi lên production.

Vấn đề 1: Không bao giờ xóa gì

Bạn đổi tên trường từ summary thành excerpt trong entity Post. Hibernate làm gì? Nó tạo cột excerpt mới nhưng để cột summary cũ trong database. Giờ bạn có hai cột — một đầy dữ liệu, một rỗng. Qua nhiều tháng, database tích lũy các cột “ma” không ai nhớ.

Trước:  posts (id, title, content, summary, ...)
Sau:    posts (id, title, content, summary, excerpt, ...)
                                    ↑ vẫn ở đây, bị bỏ rơi

Vấn đề 2: Không xử lý được thay đổi phức tạp

Đổi tên bảng? Tách một cột thành hai? Di chuyển dữ liệu từ định dạng này sang định dạng khác? Chuyển status chuỗi sang bảng enum? Hibernate update không làm được bất kỳ điều nào. Nó chỉ xử lý trường hợp đơn giản nhất: thêm cột và bảng mới.

Vấn đề 3: Không có lịch sử

Không có bản ghi ghi lại những thay đổi nào đã được áp dụng và khi nào. Nếu có lỗi, bạn không có cách biết Hibernate đã thay đổi gì hay cách hoàn tác.

Vấn đề 4: Không có quy trình review

Thay đổi schema xảy ra thầm lặng khi khởi động ứng dụng. Không ai review. Không ai phê duyệt. Một developer thêm trường vào entity, đẩy lên production, và schema database thay đổi mà không ai biết.

Vấn đề 5: Các môi trường khác nhau lệch nhau

Developer A chạy app thứ Hai và có schema phiên bản X. Developer B chạy thứ Tư và có schema phiên bản Y. Staging lần cuối cập nhật cách đây một tháng và có schema khác. Production khác tất cả. Không ai biết schema nào là “đúng.”

Giải pháp: Migration được quản lý phiên bản

Thay vì để Hibernate đoán phải làm gì, bạn viết SQL migration script rõ ràng:

  • Có phiên bản — mỗi thay đổi có số (V1, V2, V3…)
  • Được review — đi qua code review như bất kỳ code nào
  • Được theo dõi — bảng lịch sử ghi lại migration nào đã áp dụng
  • Có thể tái tạo — chạy tất cả migration từ đầu tạo ra schema giống hệt
  • Có thể hoàn tác — bạn có thể lên kế hoạch rollback cho mỗi migration

Đây là những gì Flyway cung cấp.


Database Migration là gì?

Khái niệm

Database migration là một thay đổi đơn lẻ, nguyên tử đến schema database. Mỗi migration là file SQL (hoặc class Java) biến đổi database từ một phiên bản sang phiên bản tiếp theo.

Hãy nghĩ migration là commit cho schema database. Giống như Git theo dõi mọi thay đổi đến source code, Flyway theo dõi mọi thay đổi đến database.

V1 — Tạo bảng users
V2 — Tạo bảng categories
V3 — Tạo bảng posts (với khóa ngoại đến users và categories)
V4 — Tạo bảng tags và bảng nối post_tags
V5 — Tạo bảng comments
V6 — Thêm cột views_count vào posts
V7 — Thêm index trên posts.status để tăng hiệu suất truy vấn
V8 — Đổi tên users.name thành users.full_name

Mỗi migration chạy đúng một lần. Flyway ghi lại migration nào đã áp dụng trong bảng theo dõi đặc biệt (flyway_schema_history). Khi ứng dụng khởi động, Flyway kiểm tra: “Migration mới nhất trong database là gì? Có file migration mới nào? Chạy các file mới.”

Vòng đời Migration

App khởi động
    ↓
Flyway kiểm tra bảng flyway_schema_history
    ↓
"Database đang ở V5. Các file migration có đến V8."
    ↓
Flyway chạy V6, V7, V8 theo thứ tự
    ↓
Cập nhật flyway_schema_history: V6 ✓, V7 ✓, V8 ✓
    ↓
Spring Boot tiếp tục khởi động → Hibernate kiểm tra schema
    ↓
Ứng dụng sẵn sàng

Nếu V7 thất bại (SQL sai), Flyway dừng ngay. V8 không được thực thi. Database giữ ở V6. Bạn sửa V7 và triển khai lại.


Flyway vs Liquibase — So sánh

Flyway và Liquibase là hai công cụ migration phổ biến nhất trong hệ sinh thái Java.

Tính năng Flyway Liquibase
Định dạng migration File SQL thuần XML, YAML, JSON, hoặc SQL
Độ khó học Rất thấp — chỉ viết SQL Cao hơn — cần học định dạng changelog
Tính năng đặc thù DB Truy cập đầy đủ (vì là SQL) Trừu tượng hóa — có thể không hỗ trợ hết
Tính portable giữa DB Thấp (SQL đặc thù database) Cao (định dạng trừu tượng dịch cho mọi DB)
Rollback Thủ công (viết script undo riêng) Tự sinh cho một số thay đổi
Triết lý Đơn giản và có chính kiến Linh hoạt và giàu tính năng

Khi nào chọn Flyway

  • Bạn dùng một database (MariaDB) và không dự định chuyển đổi
  • Bạn thoải mái viết SQL
  • Bạn coi trọng tính đơn giản hơn linh hoạt

Khi nào chọn Liquibase

  • Bạn hỗ trợ nhiều nhà cung cấp database
  • Bạn muốn script rollback tự sinh
  • Tổ chức yêu cầu sử dụng

Trong series này, chúng ta dùng Flyway. Đơn giản hơn, native SQL, và lựa chọn phổ biến nhất trong hệ sinh thái Spring Boot.


Thiết lập Flyway với Spring Boot

Bước 1: Thêm Dependency

<dependency>
    <groupId>org.flywaydb</groupId>
    <artifactId>flyway-core</artifactId>
</dependency>

<!-- Flyway cần module đặc thù database cho MariaDB/MySQL -->
<dependency>
    <groupId>org.flywaydb</groupId>
    <artifactId>flyway-mysql</artifactId>
</dependency>

Spring Boot auto-configure Flyway khi phát hiện dependency trên classpath. Không cần annotation hay cấu hình bean thêm — Flyway chạy tự động khi khởi động, trước khi Hibernate khởi tạo.

Bước 2: Cập nhật application.properties

# ============================================
# DataSource — giống như trước
# ============================================
spring.datasource.url=jdbc:mariadb://localhost:3306/blogdb
spring.datasource.username=bloguser
spring.datasource.password=blogpass
spring.datasource.driver-class-name=org.mariadb.jdbc.Driver

# ============================================
# JPA / Hibernate — THAY ĐỔI ddl-auto thành validate
# ============================================
# Trước: spring.jpa.hibernate.ddl-auto=update
# Giờ: Flyway quản lý schema, Hibernate chỉ kiểm tra
spring.jpa.hibernate.ddl-auto=validate

spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MariaDBDialect

# ============================================
# Cấu hình Flyway
# ============================================
# Nơi Flyway tìm file migration (mặc định là classpath:db/migration)
spring.flyway.locations=classpath:db/migration

# Bật Flyway (mặc định bật khi có trên classpath)
spring.flyway.enabled=true

# Tên bảng lịch sử schema (mặc định: flyway_schema_history)
spring.flyway.table=flyway_schema_history

Thay đổi quan trọng: spring.jpa.hibernate.ddl-auto=validate. Hibernate không còn sửa đổi schema. Nó chỉ kiểm tra entity khớp với bảng. Nếu có sai lệch, ứng dụng từ chối khởi động với thông báo lỗi rõ ràng — đây là lưới an toàn.

Bước 3: Tạo thư mục Migration

src/
└── main/
    └── resources/
        └── db/
            └── migration/
                ├── V1__create_users_table.sql
                ├── V2__create_categories_table.sql
                └── ... (thêm file migration)

Quy ước đặt tên Migration File

Flyway sử dụng tên file để xác định phiên bản và thứ tự migration. Quy ước đặt tên rất nghiêm ngặt:

V{version}__{mô_tả}.sql

Ví dụ:
V1__create_users_table.sql
V2__create_categories_table.sql
V3__create_posts_table.sql
V4__create_tags_and_post_tags.sql
V5__create_comments_table.sql
V6__add_views_count_to_posts.sql
V7__add_index_on_posts_status.sql

Các quy tắc

Tiền tố V — Đánh dấu đây là migration có phiên bản (bắt buộc).

Số phiên bản — Xác định thứ tự thực thi. Có thể là số nguyên (1, 2, 3), số thập phân (1.1, 1.2), hoặc timestamp (20240115120000).

Hai dấu gạch dưới __ — Dấu phân cách giữa phiên bản và mô tả (hai dấu gạch dưới, không phải một).

Mô tả — Tên dễ đọc mô tả thay đổi. Dùng gạch dưới thay vì khoảng trắng.

Phần mở rộng .sql — Cho Flyway biết đây là migration SQL.

Lỗi phổ biến

❌ V1_create_users.sql           → Một dấu gạch dưới (cần hai)
❌ v1__create_users.sql           → Chữ v thường (phải viết hoa V)
❌ V1__create users table.sql     → Có khoảng trắng trong tên file
❌ create_users_table.sql         → Thiếu tiền tố V
✅ V1__create_users_table.sql     → Đúng

Viết SQL Migration Script

Các quy tắc vàng

Quy tắc 1: Mỗi migration phải tự chứa. Không phụ thuộc vào Java code, file bên ngoài, hay logic ứng dụng. Là SQL thuần.

Quy tắc 2: Migration là bất biến. Một khi migration đã áp dụng vào bất kỳ môi trường nào, bạn KHÔNG BAO GIỜ thay đổi nó. Nếu cần sửa, tạo migration mới. Flyway tính checksum mỗi file — nếu file thay đổi sau khi áp dụng, Flyway từ chối khởi động.

Quy tắc 3: Mỗi migration nên là nguyên tử. Một thay đổi logic cho mỗi migration. “Tạo bảng users” là một migration. “Thêm index vào bảng posts” là migration khác. Không gộp thay đổi không liên quan.

Quy tắc 4: Luôn test migration cục bộ. Chạy migration trên database cục bộ trước khi commit.

Ví dụ Migration cho Blog

File: V1__create_users_table.sql

-- V1: Tạo bảng users
-- Đây là bảng nền tảng — các bảng khác tham chiếu qua khóa ngoại.
CREATE TABLE users (
    id              BIGINT          AUTO_INCREMENT PRIMARY KEY,
    username        VARCHAR(50)     NOT NULL,
    email           VARCHAR(255)    NOT NULL,
    password_hash   VARCHAR(255)    NOT NULL,
    full_name       VARCHAR(100),
    bio             TEXT,
    role            VARCHAR(20)     NOT NULL DEFAULT 'ROLE_USER',
    is_active       BOOLEAN         NOT NULL DEFAULT TRUE,
    created_at      TIMESTAMP       NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at      TIMESTAMP       NOT NULL DEFAULT CURRENT_TIMESTAMP
                                    ON UPDATE CURRENT_TIMESTAMP,
    UNIQUE INDEX idx_users_username (username),
    UNIQUE INDEX idx_users_email (email)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

File: V2__create_categories_table.sql

-- V2: Tạo bảng categories
CREATE TABLE categories (
    id          BIGINT          AUTO_INCREMENT PRIMARY KEY,
    name        VARCHAR(100)    NOT NULL,
    slug        VARCHAR(100)    NOT NULL,
    description TEXT,
    created_at  TIMESTAMP       NOT NULL DEFAULT CURRENT_TIMESTAMP,
    UNIQUE INDEX idx_categories_name (name),
    UNIQUE INDEX idx_categories_slug (slug)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

File: V3__create_tags_table.sql

-- V3: Tạo bảng tags
CREATE TABLE tags (
    id          BIGINT          AUTO_INCREMENT PRIMARY KEY,
    name        VARCHAR(50)     NOT NULL,
    slug        VARCHAR(50)     NOT NULL,
    created_at  TIMESTAMP       NOT NULL DEFAULT CURRENT_TIMESTAMP,
    UNIQUE INDEX idx_tags_name (name),
    UNIQUE INDEX idx_tags_slug (slug)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

File: V4__create_posts_table.sql

-- V4: Tạo bảng posts với khóa ngoại đến users và categories
CREATE TABLE posts (
    id              BIGINT          AUTO_INCREMENT PRIMARY KEY,
    title           VARCHAR(500)    NOT NULL,
    slug            VARCHAR(500)    NOT NULL,
    content         TEXT            NOT NULL,
    excerpt         VARCHAR(1000),
    status          VARCHAR(20)     NOT NULL DEFAULT 'DRAFT',
    author_id       BIGINT          NOT NULL,
    category_id     BIGINT,
    published_at    TIMESTAMP       NULL,
    created_at      TIMESTAMP       NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at      TIMESTAMP       NOT NULL DEFAULT CURRENT_TIMESTAMP
                                    ON UPDATE CURRENT_TIMESTAMP,
    UNIQUE INDEX idx_posts_slug (slug),
    INDEX idx_posts_status (status),
    INDEX idx_posts_author_id (author_id),
    FOREIGN KEY (author_id) REFERENCES users(id) ON DELETE RESTRICT,
    FOREIGN KEY (category_id) REFERENCES categories(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

File: V5__create_post_tags_table.sql

-- V5: Tạo bảng nối post_tags (nhiều-nhiều giữa posts và tags)
CREATE TABLE post_tags (
    post_id     BIGINT NOT NULL,
    tag_id      BIGINT NOT NULL,
    PRIMARY KEY (post_id, tag_id),
    FOREIGN KEY (post_id) REFERENCES posts(id) ON DELETE CASCADE,
    FOREIGN KEY (tag_id) REFERENCES tags(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

File: V6__create_comments_table.sql

-- V6: Tạo bảng comments với tự tham chiếu cho bình luận lồng nhau
CREATE TABLE comments (
    id          BIGINT      AUTO_INCREMENT PRIMARY KEY,
    content     TEXT        NOT NULL,
    post_id     BIGINT      NOT NULL,
    author_id   BIGINT      NOT NULL,
    parent_id   BIGINT      NULL,
    created_at  TIMESTAMP   NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at  TIMESTAMP   NOT NULL DEFAULT CURRENT_TIMESTAMP
                            ON UPDATE CURRENT_TIMESTAMP,
    INDEX idx_comments_post_id (post_id),
    INDEX idx_comments_author_id (author_id),
    FOREIGN KEY (post_id) REFERENCES posts(id) ON DELETE CASCADE,
    FOREIGN KEY (author_id) REFERENCES users(id) ON DELETE RESTRICT,
    FOREIGN KEY (parent_id) REFERENCES comments(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

Lệnh Flyway — Migrate, Info, Repair, Clean

flyway:migrate — Áp dụng Migration đang chờ

./mvnw flyway:migrate

Đây là lệnh quan trọng nhất. Nó tìm tất cả migration chưa áp dụng và thực thi chúng theo thứ tự. Trong Spring Boot, lệnh này chạy tự động khi ứng dụng khởi động.

flyway:info — Hiển thị trạng thái Migration

./mvnw flyway:info

Hiển thị migration nào đã áp dụng, đang chờ, hoặc thất bại. Hữu ích cho debug:

+---------+----------------------------+---------------------+---------+
| Version | Description                | Installed on        | State   |
+---------+----------------------------+---------------------+---------+
| 1       | create users table         | 2024-01-15 10:00:00 | Success |
| 2       | create categories table    | 2024-01-15 10:00:01 | Success |
| 3       | create tags table          |                     | Pending |
+---------+----------------------------+---------------------+---------+

flyway:repair — Sửa bảng lịch sử

./mvnw flyway:repair

Dùng khi migration thất bại và để lại bản ghi lỗi trong flyway_schema_history. repair xóa bản ghi thất bại để bạn có thể sửa SQL và chạy lại.

flyway:clean — Xóa mọi thứ (CHỈ DÙNG CHO DEVELOPMENT!)

./mvnw flyway:clean

CẢNH BÁO: Lệnh này XÓA TẤT CẢ bảng, view, stored procedure, và dữ liệu. Chỉ sử dụng trong development khi bạn muốn bắt đầu lại từ đầu.

Bảo vệ chống lại sử dụng vô tình:

# Ngăn flyway:clean trong production — Flyway sẽ ném lỗi
spring.flyway.clean-disabled=true

flyway:validate — Xác minh tính toàn vẹn Checksum

./mvnw flyway:validate

Kiểm tra file migration cục bộ khớp với bản ghi trong flyway_schema_history. Nếu ai đó sửa file migration đã áp dụng, validate phát hiện sự khác biệt.


Xử lý xung đột Migration trong môi trường nhóm

Vấn đề

Developer A tạo V7__add_phone_column.sql. Developer B tạo V7__add_address_column.sql. Cả hai push — giờ có hai file V7!

Chiến lược 1: Phiên bản dựa trên Timestamp

Thay vì số tuần tự, dùng timestamp:

V20240115120000__add_phone_column.sql    (Developer A: 15/1 lúc 12:00)
V20240115143000__add_address_column.sql  (Developer B: 15/1 lúc 14:30)

Xung đột gần như không thể xảy ra — hai developer sẽ không tạo migration cùng giây.

Chiến lược 2: Giao tiếp trong nhóm

Đồng ý trên số phiên bản tiếp theo trong kênh Slack trước khi tạo migration.

Chiến lược 3: Để CI phát hiện

Thêm kiểm tra CI phát hiện số phiên bản trùng. Nếu hai file migration chia sẻ phiên bản, build CI thất bại trước khi merge vào main.

Khuyến nghị

Nhóm nhỏ (2-5 developer): số tuần tự với giao tiếp đủ dùng. Nhóm lớn hơn: phiên bản timestamp loại bỏ xung đột hoàn toàn.


Seed Data và Repeatable Migration

Seed Data với Versioned Migration

Cho dữ liệu tham chiếu ban đầu (category, tag, role), bao gồm trong versioned migration:

File: V100__seed_initial_data.sql

-- V100: Seed dữ liệu tham chiếu ban đầu
-- Dùng V100 để có chỗ cho migration schema trước đó.

-- Danh mục mặc định
INSERT INTO categories (name, slug, description) VALUES
    ('Tutorial', 'tutorial', 'Hướng dẫn lập trình từng bước'),
    ('News', 'news', 'Tin tức công nghệ mới nhất'),
    ('Opinion', 'opinion', 'Suy nghĩ và quan điểm cá nhân'),
    ('Review', 'review', 'Đánh giá sản phẩm và công nghệ');

-- Tag mặc định
INSERT INTO tags (name, slug) VALUES
    ('Java', 'java'),
    ('Spring Boot', 'spring-boot'),
    ('MariaDB', 'mariadb'),
    ('Docker', 'docker'),
    ('REST API', 'rest-api'),
    ('JPA', 'jpa'),
    ('Security', 'security'),
    ('Testing', 'testing');

-- Admin user (mật khẩu: admin123 — đã hash BCrypt)
-- Trong production, tạo admin user qua quy trình bảo mật, không trong migration.
INSERT INTO users (username, email, password_hash, full_name, role) VALUES
    ('admin', 'admin@blog.com', '$2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy',
     'System Admin', 'ROLE_ADMIN');

Repeatable Migration

Flyway hỗ trợ repeatable migration — file được chạy lại mỗi khi nội dung thay đổi. Chúng dùng tiền tố R__ thay vì V{số}__:

R__create_or_replace_views.sql
R__refresh_materialized_data.sql

Repeatable migration chạy sau tất cả versioned migration, theo thứ tự bảng chữ cái. Chúng hữu ích cho:

  • Database view được tạo lại khi schema bên dưới thay đổi
  • Stored procedure hoặc function
  • Dữ liệu tham chiếu thay đổi thường xuyên

File: R__create_post_statistics_view.sql

-- Repeatable: view này được tạo lại mỗi khi file thay đổi
DROP VIEW IF EXISTS post_statistics;

CREATE VIEW post_statistics AS
SELECT
    p.id AS post_id,
    p.title,
    p.status,
    u.username AS author,
    c.name AS category,
    p.views_count,
    (SELECT COUNT(*) FROM comments cm WHERE cm.post_id = p.id) AS comment_count,
    (SELECT COUNT(*) FROM post_tags pt WHERE pt.post_id = p.id) AS tag_count,
    p.published_at,
    p.created_at
FROM posts p
JOIN users u ON p.author_id = u.id
LEFT JOIN categories c ON p.category_id = c.id;

Mỗi khi bạn sửa file này, Flyway phát hiện checksum thay đổi và chạy lại khi khởi động tiếp theo.

Seed Data chỉ cho Development

Dùng vị trí Flyway riêng cho từng profile:

# application-dev.properties
spring.flyway.locations=classpath:db/migration,classpath:db/devdata

# application-prod.properties
spring.flyway.locations=classpath:db/migration

Đặt seed data development trong src/main/resources/db/devdata/. Nó chỉ chạy trong profile dev.


Thực hành: Migrate Blog Database từ DDL-Auto sang Flyway

Bước 1: Xử lý Database hiện có

Lựa chọn A: Bắt đầu sạch (development — dễ nhất)

# Xóa và tạo lại database
docker exec -it blogdb mariadb -u root -prootpass \
  -e "DROP DATABASE IF EXISTS blogdb; CREATE DATABASE blogdb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; GRANT ALL PRIVILEGES ON blogdb.* TO 'bloguser'@'%'; FLUSH PRIVILEGES;"

Khởi động ứng dụng. Flyway tạo tất cả bảng từ đầu.

Lựa chọn B: Baseline database hiện có (production — khi dữ liệu quan trọng)

# Cho Flyway biết database hiện có đại diện phiên bản V6
spring.flyway.baseline-on-migrate=true
spring.flyway.baseline-version=6
spring.flyway.baseline-description=Baseline existing schema

Flyway chèn bản ghi “baseline” vào flyway_schema_history và bỏ qua V1-V6. Migration tương lai (V7, V8, …) sẽ được áp dụng bình thường.

Bước 2: Khởi động và Xác minh

./mvnw spring-boot:run

Theo dõi output console:

Flyway Community Edition 9.x by Redgate
Database: jdbc:mariadb://localhost:3306/blogdb (MariaDB 11.x)
Successfully validated 7 migrations
Creating Schema History table `blogdb`.`flyway_schema_history` ...
Migrating schema `blogdb` to version "1 - create users table"
Migrating schema `blogdb` to version "2 - create categories table"
...
Successfully applied 7 migrations to schema `blogdb`

Bước 3: Thêm Migration mới

Mô phỏng thêm tính năng — cột views_count vào bảng posts.

File: V7__add_views_count_to_posts.sql

-- V7: Thêm cột views_count để theo dõi độ phổ biến bài viết
ALTER TABLE posts ADD COLUMN views_count INT NOT NULL DEFAULT 0;
CREATE INDEX idx_posts_views_count ON posts(views_count);

Cập nhật entity Post:

@Column(name = "views_count", nullable = false)
private int viewsCount = 0;

Khởi động lại. Flyway áp dụng V7 tự động, Hibernate validate thành công.

Bước 4: Kiểm thử toàn bộ quy trình

# Tạo bài viết
curl -X POST http://localhost:8080/api/posts \
  -H "Content-Type: application/json" \
  -d '{"title":"Hướng dẫn Flyway","content":"Học quản lý migration...","authorId":1,"categoryId":1}'

# Xác minh bài viết
curl http://localhost:8080/api/posts/1

# Dừng và khởi động lại ứng dụng
# Flyway output: "Current version: 7" — không có migration mới
# Tất cả dữ liệu được bảo toàn

# Xác minh dữ liệu tồn tại sau khởi động lại
curl http://localhost:8080/api/posts/1

Bài tập

  1. Thêm bảng user_profiles: Viết V8__create_user_profiles_table.sql tạo bảng user_profiles từ Bài 7. Khởi động ứng dụng và xác minh bảng được tạo.
  2. Thêm cột với data migration: Viết V9__add_word_count_to_posts.sql thêm cột word_count INT và điền dữ liệu dựa trên độ dài nội dung.
  3. Tạo repeatable migration: Viết R__create_post_statistics_view.sql. Sửa view và khởi động lại — xác minh nó chạy lại.
  4. Mô phỏng migration thất bại: Viết migration với SQL sai cố ý. Khởi động và quan sát lỗi. Sửa SQL, chạy flyway:repair, và khởi động lại.
  5. Thực hành baseline: Tạo database mới, tạo bảng users thủ công, rồi cấu hình Flyway baseline tại V1 và áp dụng V2 trở đi.

Tổng kết

  • Tại sao ddl-auto=update thất bại trong production: Không bao giờ xóa cột, không xử lý thay đổi phức tạp, không có lịch sử, không có review, gây lệch giữa các môi trường.
  • Database migration: Mỗi thay đổi schema là file SQL có phiên bản, được theo dõi trong bảng lịch sử, thực thi đúng một lần, theo thứ tự.
  • Flyway vs Liquibase: Flyway đơn giản hơn (SQL thuần), Liquibase linh hoạt hơn (định dạng trừu tượng). Flyway là lựa chọn tốt hơn cho dự án Spring Boot đơn database.
  • Tích hợp Spring Boot: Thêm dependency flyway-coreflyway-mysql, đặt ddl-auto=validate, đặt file SQL trong db/migration/. Flyway chạy tự động khi khởi động.
  • Quy ước đặt tên: V{phiên_bản}__{mô_tả}.sql — V viết hoa, số phiên bản, hai gạch dưới, tên mô tả, phần mở rộng .sql.
  • Quy tắc migration: SQL tự chứa, bất biến khi đã áp dụng, một thay đổi logic mỗi file, luôn test cục bộ.
  • Lệnh Flyway: migrate (áp dụng pending), info (hiển thị trạng thái), repair (sửa bản ghi lỗi), clean (xóa mọi thứ — chỉ dev), validate (kiểm tra checksum).
  • Repeatable migration: Tiền tố R__ cho view, procedure, và dữ liệu tham chiếu cần chạy lại khi thay đổi.
  • Baseline: Cho Flyway biết database hiện có đại diện phiên bản cụ thể, rồi quản lý thay đổi tương lai bằng migration.

Tham chiếu nhanh

Khái niệm Mô tả
Database Migration Thay đổi schema có phiên bản, được theo dõi — “commit” cho database
Flyway Công cụ migration native SQL, tự động chạy trong Spring Boot
flyway_schema_history Bảng theo dõi migration đã áp dụng
V{n}__{desc}.sql File migration có phiên bản (chạy một lần, theo thứ tự)
R__{desc}.sql Repeatable migration (chạy lại khi nội dung thay đổi)
ddl-auto=validate Hibernate kiểm tra schema nhưng không sửa đổi
flyway:migrate Áp dụng migration đang chờ
flyway:info Hiển thị trạng thái migration
flyway:repair Xóa bản ghi migration thất bại
flyway:clean Xóa tất cả đối tượng database (chỉ development!)
flyway:validate Xác minh tính toàn vẹn checksum của migration đã áp dụng
spring.flyway.baseline-on-migrate Baseline database hiện có
spring.flyway.clean-disabled Ngăn clean vô tình trong production
Checksum Hash nội dung file migration — phát hiện thay đổi trái phép
Quy tắc bất biến Không bao giờ sửa migration sau khi đã áp dụng

Để lại một bình luận

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *