- 后端
- 数据库
- ORM
【免费下载链接】sea-orm
🐚 A powerful relational ORM for Rust
本篇技术指南基于 sea-orm 仓库中的 examples/poem_example 示例项目,完整讲解 SeaORM 迁移(Migration)子系统在真实 Web 应用中的落地方式:如何通过 Migrator CLI 应用、回滚、重置与查询迁移状态,并结合 Poem 示例的迁移模块源码 深入剖析up/down/fresh/refresh/reset/status六类命令的底层执行逻辑与调用链。读完本文,你将掌握 SeaORM 迁移的 CLI 操作全流程,并能看懂迁移代码的结构与程序内自动迁移的触发机制。
关联文档与仓库定位
本指南的核心文档是 examples/poem_example/migration/README.md,它是一份浓缩的 Migrator CLI 操作速查表,罗列了迁移生命周期中最常用的七个命令场景。该文档所在的migrationcrate 是 Poem Web 示例(一个基于 Tera 模板的博客应用)的数据库迁移模块,与其平级的还有api(Poem 服务端)、entity(实体定义)两个 crate,共同组成一个完整工作区,定义见 examples/poem_example/Cargo.toml。
需要说明的是,SeaORM 迁移 CLI 的行为由sea-orm-migrationcrate 统一提供,因此本文讲解的命令与原理同样适用于仓库中其他示例(如 axum_example/migration、rocket_example/migration 等),下文将以 Poem 示例为具体载体展开。
迁移模块的项目结构
在动手运行命令之前,先看清迁移模块的组成。Poem 示例的迁移 crate 包含 4 个关键文件:
examples/poem_example/migration/ ├── Cargo.toml # 依赖声明与 feature 配置 └── src/ ├── lib.rs # Migrator 聚合器:注册全部迁移 ├── main.rs # CLI 入口:调用 cli::run_cli ├── m20220120_000001_create_post_table.rs # 迁移 1:创建 post 表 └── m20220120_000002_seed_posts.rs # 迁移 2:写入种子数据其中 src/lib.rs 是整个迁移体系的枢纽:它通过MigratorTrait实现Migrator结构体,在migrations()方法中按顺序返回迁移列表:
pub struct Migrator; #[async_trait::async_trait] impl MigratorTrait for Migrator { fn migrations() -> Vec<Box<dyn MigrationTrait>> { vec![ Box::new(m20220120_000001_create_post_table::Migration), Box::new(m20220120_000002_seed_posts::Migration), ] } }而 src/main.rs 则极为精简——全部 CLI 逻辑都委托给sea-orm-migration提供的cli::run_cli:
use sea_orm_migration::prelude::*; #[tokio::main] async fn main() { cli::run_cli(migration::Migrator).await; }这意味着:只要你的迁移 crate 实现了MigratorTrait并像上面这样编写入口,就能直接获得一整套迁移命令行工具,无需自己解析参数。
环境准备:数据库连接与依赖配置
Migrator CLI 通过环境变量读取数据库连接信息。从 sea-orm-migration/src/cli.rs 的Cli结构体定义可以看出,CLI 支持以下全局参数:
| 参数 | 环境变量 | 说明 |
|---|---|---|
-u,--database-url | DATABASE_URL | 数据库连接 URL,必填;未设置时会报错Environment variable 'DATABASE_URL' not set |
-s,--database-schema | DATABASE_SCHEMA | 数据库 schema 名;PostgreSQL 下可选、默认public,MySQL 与 SQLite 下被忽略 |
-v,--verbose | — | 输出 debug 级别的日志 |
在运行迁移命令前,通常先在工作区根目录创建.env文件写入DATABASE_URL。CLI 启动时会通过dotenv().ok()自动加载.env(见 cli.rs 的run_cli_with_connection函数)。Poem 示例默认使用 SQLite,因此典型的配置形如:
DATABASE_URL=sqlite://posts.db?mode=rwc?mode=rwc表示读写并在不存在时自动创建数据库文件。若使用 PostgreSQL,还需在DATABASE_SCHEMA中指定 schema(默认public)。
依赖配置上,migration/Cargo.toml 给出了两个关键点:
[dependencies.sea-orm-migration] features = [ # Enable following runtime and db backend features if you want to run migration via CLI "runtime-tokio-native-tls", "sqlx-sqlite", ] path = "../../../sea-orm-migration" # remove this line in your own project version = "~2.0.3" # sea-orm-migration version- 运行时与数据库后端 feature 必须匹配你的目标数据库:注释明确提示,若要经由 CLI 运行迁移,需要启用对应的 runtime(如
runtime-tokio-native-tls)与 db backend(如sqlx-sqlite、sqlx-postgres)feature; path指向的是本仓库内的 crate:在独立项目中应删除这一行,仅保留version;- 此外还声明了对
entity与tokio的依赖,后者为 CLI 的异步运行时提供支持。
Migrator CLI 命令速查:完整继承原文档
原 README 文档给出的全部命令如下,每一条均可在迁移模块目录下直接运行:
应用全部待执行的迁移
cargo runcargo run -- up应用前 10 个待执行的迁移
cargo run -- up -n 10回滚最后应用的迁移
cargo run -- down回滚最后 10 个已应用的迁移
cargo run -- down -n 10删除数据库中的全部表,然后重新应用所有迁移
cargo run -- fresh回滚所有已应用的迁移,然后重新应用所有迁移
cargo run -- refresh回滚所有已应用的迁移
cargo run -- reset检查所有迁移的状态
cargo run -- status关于-n参数需要补充两点语义:其一,up -n 10中的-n意为 "number of migrations to be applied",即本次最多应用 10 个待执行迁移;其二,down -n 10表示回滚最近应用的 10 个迁移。从 cli.rs 的run_migrate_inner可以看到,down分支会把num以Some(num)传入,而up分支(默认分支)在无子命令时以None表示“应用全部”。
命令底层的执行逻辑与调用链
上述每个命令最终都落在MigratorTrait的方法上。对照 cli.rs 中run_migrate_inner的匹配逻辑:
match command { Some(MigrateSubcommands::Fresh) => migrator.fresh(db).await?, Some(MigrateSubcommands::Refresh) => migrator.refresh(db).await?, Some(MigrateSubcommands::Reset) => migrator.reset(db).await?, Some(MigrateSubcommands::Status) => migrator.status(db).await?, Some(MigrateSubcommands::Up { num }) => migrator.up(db, num).await?, Some(MigrateSubcommands::Down { num }) => migrator.down(db, Some(num)).await?, _ => migrator.up(db, None).await?, }各命令的语义差异可以这样理解:
| 命令 | 底层方法 | 行为 |
|---|---|---|
cargo run(无参数)/up | MigratorTrait::up | 按注册顺序应用所有未执行的迁移;每次迁移调用其up()方法 |
up -n 10 | MigratorTrait::up(num=10) | 只应用前 10 个待执行迁移 |
down/down -n 10 | MigratorTrait::down | 逆序回滚最近应用的迁移,逐个调用迁移的down()方法 |
fresh | MigratorTrait::fresh | 先删除库中全部表(等价于重置到空库),再应用全部迁移 |
refresh | MigratorTrait::refresh | 先回滚全部已应用迁移,再重新应用全部迁移 |
reset | MigratorTrait::reset | 仅回滚全部已应用迁移,不重新应用 |
status | MigratorTrait::status | 列出每个迁移的待执行/已应用状态,不修改数据库 |
其中fresh、refresh、reset三者的差别值得重点记忆:fresh走的是“删表”路线,refresh走的是“回滚再应用”路线,而reset只回滚不重建——日常开发中refresh是最常用的“重建数据库”方式,因为它能同时执行迁移的down与up两个方向的代码,可更完整地验证迁移的可逆性。
此外,CLI 还支持两个无需数据库连接的子命令(见run_non_db_command):init(初始化迁移目录)与generate(生成新的迁移文件模板),它们在 Poem 示例中未直接使用,但在用sea-orm-cli从零搭建项目时非常有用。
从源码看迁移如何被执行
以 Poem 示例的第一个迁移 m20220120_000001_create_post_table.rs 为例,它展示了迁移文件的标准骨架:
#[derive(DeriveMigrationName)] pub struct Migration; #[async_trait::async_trait] impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { manager .create_table( Table::create() .table("post") .if_not_exists() .col(pk_auto("id")) .col(string("title")) .col(string("text")) .to_owned(), ) .await } async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { manager .drop_table(Table::drop().table("post").to_owned()) .await } }要点拆解:
#[derive(DeriveMigrationName)]自动根据类型名Migration派生迁移名,与文件前缀的m20220120_000001时间戳配合,形成全局唯一、按时间排序的迁移标识;up()使用sea_orm_migration::schema提供的辅助函数(如pk_auto、string)配合Table::create()构建建表语句,if_not_exists()保证重复执行安全;down()是对称的撤销操作,调用drop_table删除post表。fresh/refresh/reset等命令能工作,前提就是每个迁移都实现了可逆的down()。
第二个迁移 m20220120_000002_seed_posts.rs 演示了如何在迁移中执行数据操作(种子数据):
let db = manager.get_connection(); let seed_data = vec![ ("First Post", "This is the first post."), ("Second Post", "This is another post."), ]; for (title, text) in seed_data { let model = post::ActiveModel { title: Set(title.to_string()), text: Set(text.to_string()), ..Default::default() }; model.insert(db).await?; }关键点在于:迁移不仅能改表结构,还能通过manager.get_connection()拿到数据库连接,直接以ActiveModel的方式插入记录——这也是为什么种子数据、字典表初始化等任务可以自然地放进迁移脚本中。对应的down()则通过post::Entity::delete_many().filter(post::Column::Title.is_in(...))按标题精确清理种子数据,保证回滚后数据状态与迁移前一致。
程序内自动迁移:不依赖 CLI 的另一种执行方式
值得注意的一个细节是,Poem 示例的 Web 服务在启动时就会自动执行迁移,而无需手动运行 CLI。见 api/src/lib.rs 的启动流程:
// create post table if not exists let conn = Database::connect(&db_url).await.unwrap(); Migrator::up(&conn, None).await.unwrap();这段代码在服务启动后直接调用Migrator::up,将所有未应用的迁移应用一遍(迁移表内部保证幂等,已应用的会跳过)。这是一种非常实用的生产部署模式:把迁移与应用启动绑定,省去额外运维步骤。它说明MigratorTrait的方法既可以被 CLI 包装调用,也可以在你的业务代码里直接调用,两条路径共用同一套迁移列表与幂等机制。
因此在实际项目中,你通常有两种选择:
- 开发阶段:在迁移模块目录下使用本文速查表中的 CLI 命令(
up/down/fresh等),随时调整 Schema; - 部署阶段:像 Poem 示例这样在应用启动时调用
Migrator::up(&conn, None),让迁移随服务一起完成。
两者可以共存——CLI 管理的迁移状态表与程序内执行的迁移状态表是同一份,不会互相冲突。
小结
本文以 examples/poem_example/migration/README.md 的命令速查表为骨架,结合 Poem 示例的迁移源码,完整覆盖了 SeaORM Migrator CLI 的七个核心命令场景,并深入到了sea-orm-migration的 CLI 实现层:从DATABASE_URL/DATABASE_SCHEMA的连接配置,到up/down/fresh/refresh/reset/status与MigratorTrait方法的映射关系,再到迁移文件up()/down()的编写范式与程序内自动迁移的用法。掌握这些内容后,无论你是要在新项目中用 SeaORM 搭建 Schema 管理,还是接手现有 sea-orm 工程,都能准确、安全地执行每一次迁移操作。
- 后端
- 数据库
- ORM
【免费下载链接】sea-orm
🐚 A powerful relational ORM for Rust
相关推荐
Iosevka 27.0.1 新字符详解:VERY MUCH LESS-THAN / GREATER-THAN(U+22D8 / U+22D9)的源码实现与构建验证
Iosevka 27.0.1 新字符详解:VERY MUCH LESS THAN / GREATER THAN(U+22D8 / U+22D9)的源码实现与构建
后端数据库ORMSubstrate 依赖解析:zeebo/xxh3 在 Go 中的 XXH3 非加密哈希算法与性能基准实战
Substrate 依赖解析:zeebo/xxh3 在 Go 中的 XXH3 非加密哈希算法与性能基准实战 本篇文章聚焦当前仓库 substrate 中作为间接
人工智能AI AgentAgent 沙箱云原生容器运行时零信任SeaORM Migrator CLI 迁移命令完全指南:生成、应用、回滚与状态管理实战
SeaORM Migrator CLI 迁移命令完全指南:生成、应用、回滚与状态管理实战 导读 本文以 examples/loco_example/migrat
后端数据库ORM
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考