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
- Thêm bảng user_profiles: Viết
V8__create_user_profiles_table.sqltạo bảnguser_profilestừ Bài 7. Khởi động ứng dụng và xác minh bảng được tạo. - Thêm cột với data migration: Viết
V9__add_word_count_to_posts.sqlthêm cộtword_countINT và điền dữ liệu dựa trên độ dài nội dung. - 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. - 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. - 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-corevàflyway-mysql, đặtddl-auto=validate, đặt file SQL trongdb/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 |
