第 12 章 · golang-migrate 迁移与种子数据
本章目标:在 shop-db 中集成 golang-migrate;编写 V1__init.sql 迁移 ch07 完整 DDL;配置 application 或 go mod 插件 执行 migrate;编写 幂等种子数据 Go/SQL 脚本;理解 validate + golang-migrate 与 GORM 协作;对照 python-database ch11 Alembic 工作流异同。
学时建议:5~6 小时(含 2 小时迁移跟练)
前置:完成 go-database ch07(DDL 基线)与 ch10(GORM validate);了解 python-database ch11(Alembic)。
12.1 场景说明:schema 必须可版本化
| 痛点 | 手工 SQL | golang-migrate |
|---|---|---|
| 多人改表冲突 | 难以合并 | db/migration/V*.sql 可 code review |
| 生产升级 | 手工执行易漏 | flyway migrate |
| 回滚 | 无标准 | 社区版靠补偿脚本;或 undo(Teams) |
| 与 GORM 同步 | 易漂移 | golang-migrate 建表 + GORM validate |
shop-db 演进示例:
- V1:ch07 六表 + 购物车
- V2:ch13 增加
tags/product_tags(练习) - V3:
products增加cover_url(练习)
对照 python-database ch11 Alembic:db-demo 用 autogenerate 对比 ORM metadata;shop-db 用 手写 SQL 迁移(Go 生态常见,与 Spring Boot 默认一致)。
12.2 golang-migrate 与 Alembic 概念对照
| 概念 | golang-migrate (Go) | Alembic (Python) |
|---|---|---|
| 版本表 | flyway_schema_history | alembic_version |
| 脚本位置 | db/migration/V1__xxx.sql | alembic/versions/*.py |
| 执行命令 | flyway migrate | alembic upgrade head |
| 回滚 | 手写 V{n+1} 补偿 | downgrade -1 |
| 自动生成 | 无(手写 SQL) | revision --autogenerate |
| Spring 集成 | spring.flyway.* | 需手动/FastAPI 启动钩子 |
共同点:版本链单调递增;禁止修改已发布到 main 的历史脚本。
12.3 go mod 集成 golang-migrate
go.mod:
<properties>
<flyway.version>10.10.0</flyway.version>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-maven-plugin</artifactId>
<version>${flyway.version}</version>
<configuration>
<url>mysql DSNmysql://127.0.0.1:3306/shop_db?useSSL=false&serverTimezone=Asia/Shanghai</url>
<user>shop_user</user>
<password>shop_pass</password>
<locations>
<location>classpath:db/migration</location>
</locations>
</configuration>
</plugin>
</plugins>
</build>
目录结构:
~/go-learn/shop-db/
├── src/main/resources/db/migration/
│ ├── V1__init.sql
│ └── V2__add_tags.sql # 练习,可选
├── src/main/resources/db/seed/
│ └── R__seed_shop.sql # Repeatable 种子(可选)
└── go.mod
| 前缀 | 含义 |
|---|---|
V{version}__{description}.sql | 版本化迁移,只执行一次 |
R__{description}.sql | 可重复执行(checksum 变则重跑) |
U{version}__ | Undo(golang-migrate Teams) |
12.4 V1__init.sql 完整 DDL
将 ch07 §7.4~§7.8 合并为 V1__init.sql:
-- V1__init.sql
-- shop-db baseline schema (go-database ch07/ch12)
-- charset: utf8mb4, engine: InnoDB
CREATE TABLE IF NOT EXISTS categories (
id BIGINT NOT NULL AUTO_INCREMENT,
name VARCHAR(64) NOT NULL,
slug VARCHAR(64) NOT NULL,
sort_order INT NOT NULL DEFAULT 0,
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3)
ON UPDATE CURRENT_TIMESTAMP(3),
PRIMARY KEY (id),
UNIQUE KEY uk_categories_slug (slug),
KEY idx_categories_sort (sort_order, id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS products (
id BIGINT NOT NULL AUTO_INCREMENT,
category_id BIGINT NOT NULL,
title VARCHAR(128) NOT NULL,
slug VARCHAR(128) NOT NULL,
description TEXT NULL,
price BIGINT NOT NULL COMMENT '单价,单位:分',
stock INT NOT NULL DEFAULT 0,
is_published TINYINT(1) NOT NULL DEFAULT 0,
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3)
ON UPDATE CURRENT_TIMESTAMP(3),
PRIMARY KEY (id),
UNIQUE KEY uk_products_slug (slug),
KEY idx_products_category_published (category_id, is_published, created_at DESC),
KEY idx_products_published_price (is_published, price),
CONSTRAINT fk_products_category
FOREIGN KEY (category_id) REFERENCES categories(id)
ON DELETE RESTRICT ON UPDATE CASCADE,
CONSTRAINT chk_products_price CHECK (price >= 0),
CONSTRAINT chk_products_stock CHECK (stock >= 0)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- users, orders, order_lines, carts, cart_items 同 ch07,此处省略以节省篇幅
-- 跟练时必须写全 7 张表
跟练要求:V1__init.sql必须包含 ch07 全部 7 表,不得省略users/orders/order_lines/carts/cart_items。
与 ch07 shop_db.sql 关系:
| 文件 | 用途 |
|---|---|
ch07 schema/shop_db.sql | 学习 DDL、手动执行 |
ch12 V1__init.sql | 版本化权威来源,golang-migrate 执行 |
内容应一致;后续只改 V2__ 增量,不改 V1__(已发布则禁止改)。
12.5 执行迁移
cd ~/go-learn/shop-db
# 确保空库或仅 flyway 表
mysql -h 127.0.0.1 -u shop_user -pshop_pass -e "CREATE DATABASE IF NOT EXISTS shop_db;"
# go mod 插件
mvn -q flyway:migrate
# 验证
mysql -h 127.0.0.1 -u shop_user -pshop_pass shop_db -e "SHOW TABLES;"
mysql -h 127.0.0.1 -u shop_user -pshop_pass shop_db \
-e "SELECT installed_rank, version, description, success FROM flyway_schema_history;"
| 命令 | 作用 |
|---|---|
flyway:migrate | 应用未执行版本 |
flyway:info | 查看 pending/applied |
flyway:validate | 校验 checksum |
flyway:clean | 删库全部对象,仅 dev! |
12.6 纯 Go 启动 golang-migrate(无 go mod 插件)
package com.example.shopdb.flyway;
import org.flywaydb.core.golang-migrate;
import gox.sql.DataSource;
import com.example.shopdb.pool.HikariDataSourceFactory;
public class golang-migrateMigrator {
public static void migrate() {
DataSource ds = HikariDataSourceFactory.create();
golang-migrate flyway = golang-migrate.configure()
.dataSource(ds)
.locations("classpath:db/migration")
.load();
flyway.migrate();
}
public static void main(String[] args) {
migrate();
System.out.println("golang-migrate migrate 完成");
}
}
go mod 依赖:
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
<version>10.10.0</version>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-mysql</artifactId>
<version>10.10.0</version>
</dependency>
启动顺序(shop-db 应用):
1. golang-migrate.migrate() → 表结构最新
2. GORM EMF validate → 实体与表一致
3. 业务逻辑 → Repository / gorm.DB
4. (可选)SeedRunner → 种子数据