SeaORM 与 Seaography 实战:用 Rust 从数据库一键生成 GraphQL API
2026/9/24 16:10:56 网站建设 项目流程
  • 后端
  • 数据库
  • ORM

【免费下载链接】sea-orm

🐚 A powerful relational ORM for Rust

项目地址:https://gitcode.com/gh_mirrors/se/sea-orm
点击查看免费下载

导读

本文基于 SeaORM 仓库中的 seaography_example 完整示例,系统讲解如何将 SeaORM 实体模型与 Seaography 结合,从现有数据库(Bakery 面包店 Schema)一键生成可运行的 GraphQL API 服务。你将掌握:如何初始化数据库并运行迁移、如何安装sea-orm-cliseaography-cli两条 CLI 工具链、如何用代码生成器产出 GraphQL 工程,以及如何在 GraphQL Playground 中编写筛选、聚合与多级关联查询。读完本文,你可以在自己的 Rust 项目中复刻"数据库 → 实体 → GraphQL 服务"的完整流水线。

示例概览:Bakery 面包店 Schema

整个示例围绕一个面包店业务模型展开,涉及 6 张业务表与 1 张多对多关联表,仓库中已经包含了迁移定义、种子数据与已生成的 SQLite 数据库文件bakery.db。核心实体及其关系如下:

  • bakery(面包店)idnameprofit_margin,拥有多个bakers与多个cakes
  • baker(面包师)idnamecontactbakery_id(可空),通过cake_bakercakes多对多关联;
  • cake(蛋糕)idnameprice(Decimal)、bakery_idgluten_free,通过cake_bakerbakers多对多关联;
  • cake_baker(关联表)cake_id+baker_id复合主键,用于表达"蛋糕由哪些面包师制作";
  • customer、order、lineitem:顾客、订单与订单行明细,构成完整的零售业务链路。

从生成的实体代码可以看到 SeaORM 的关系定义方式(cake.rs):

#[sea_orm::model] #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "cake")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, pub name: String, #[sea_orm(column_type = "Decimal(Some((16, 4)))")] pub price: Decimal, pub bakery_id: i32, pub gluten_free: bool, #[sea_orm( belongs_to, from = "bakery_id", to = "id", on_update = "Cascade", on_delete = "Cascade" )] pub bakery: BelongsTo<super::bakery::Entity>, #[sea_orm(has_many, via = "cake_baker")] pub bakers: HasMany<super::baker::Entity>, } impl ActiveModelBehavior for ActiveModel {}

正是这些belongs_to/has_many/has_many(via = ...)声明,让 Seaography 在构建 GraphQL Schema 时能够自动推导出可嵌套查询的对象关系。

运行示例项目

1. 指定数据库连接

示例默认使用 SQLite 数据库,通过环境变量DATABASE_URL指定连接地址。仓库已自带bakery.db,直接用只读模式连接即可:

export DATABASE_URL="sqlite://../bakery.db"

连接 URL 基于graphql目录解析,因此../bakery.db指向示例根目录下的数据库文件。若希望以读写模式打开(例如用于后续重新跑迁移),可改为:

export DATABASE_URL="sqlite://../bakery.db?mode=rwc"

2. 启动 GraphQL 服务

进入生成好的 GraphQL 工程并直接运行:

cd graphql cargo run

服务启动入口位于 main.rs,它通过sea-ormDatabase::connect建立连接,然后调用query_root::schema构建 Schema,最后用axum在默认地址localhost:8000暴露 GraphQL 端点:

let db = Database::connect(&*DATABASE_URL) .await .expect("Fail to initialize database connection"); let schema = sea_orm_seaography_example::query_root::schema(db, *DEPTH_LIMIT, *COMPLEXITY_LIMIT) .unwrap(); let app = Router::new() .route(&*ENDPOINT, get(graphql_playground).post(graphql_handler)) .with_state(schema); println!("Visit GraphQL Playground at http://{}", *URL); axum::serve(TcpListener::bind(&*URL).await.unwrap(), app) .await .unwrap();

这里还支持三个可选环境变量,均用于 GraphQL 服务的运行期控制:

环境变量默认值作用
URLlocalhost:8000服务监听地址,浏览器访问 GraphQL Playground 的入口
ENDPOINT/GraphQL 端点路径,Playground 与查询请求都挂在它下面
DEPTH_LIMIT不限制限制查询嵌套深度,防止恶意深嵌套查询
COMPLEXITY_LIMIT不限制限制单次查询的复杂度,保护后端数据库

启动后打开http://localhost:8000即可看到 GraphQL Playground 界面(本示例开启了graphql-playgroundfeature)。

运行示例中的 GraphQL 查询

Seaography 为每个实体自动生成对应的根查询字段,同时内置了filters(过滤)、having(聚合后过滤)、orderBy(排序)、pagination(分页)等标准入参。下面三个查询与文档一致,可在 Playground 中直接执行验证。

查询 1:查找巧克力蛋糕及其在售面包店

{ cake(filters: { name: { contains: "Chocolate" } }) { nodes { name price bakery { name } } } }

filters支持字符串的contains(包含)、eq(等于)、startsWithendsWith等运算;返回结果通过nodes承载数据行,并可沿cake -> bakery的 belongs_to 关系继续取面包店名称。

该查询在仓库测试 query_tests.rs 中被完整断言过,预期结果包含 SeaSide Bakery 的两款巧克力蛋糕与 LakeSide Bakery 的一款,例如:

{ "cake": { "nodes": [ { "name": "Chocolate Cake", "price": "10.25", "bakery": { "name": "SeaSide Bakery" } }, { "name": "Double Chocolate", "price": "12.5", "bakery": { "name": "SeaSide Bakery" } }, { "name": "Double Chocolate", "price": "12.5", "bakery": { "name": "LakeSide Bakery" } } ] } }

查询 2:查找 Alice 烘焙的所有蛋糕

{ cake(having: { baker: { name: { eq: "Alice" } } }) { nodes { name price baker { nodes { name } } } } }

having用于"按关联实体条件筛选聚合结果":这里要求目标蛋糕必须存在名为Alice的关联面包师(经由多对多表cake_baker)。值得注意的是,外层cake与内层baker的返回结构不同——cake.baker是多对多关系,因此以nodes列表形式返回;而查询 1 中cake.bakery属于一对一(belongs_to)关系,直接返回对象即可。

查询 3:面包店 → 蛋糕 → 面包师三级嵌套

{ bakery(pagination: { page: { limit: 10, page: 0 } }, orderBy: { name: ASC }) { nodes { name cake { nodes { name price baker { nodes { name } } } } } } }

这个查询同时演示了pagination(每页 10 条、第 0 页)与orderBy(按名称升序)两种入参,并沿bakery -> cake -> baker三级关系嵌套取数,充分体现 Seaography 将 SeaORM 关系模型直接映射为 GraphQL 对象图的能力。注意分页入参中page字段被复用了两次,外层为分页对象、内层page为页码,使用时需区分。

从零搭建:完整的生成流程

如果不使用仓库自带的生成结果,可以按下面四步从头搭建一个属于自己的 SeaORM + Seaography GraphQL 工程。

第一步:准备数据库与迁移

进入migration目录,其 README 提供了完整说明。设置数据库并应用全部迁移:

export DATABASE_URL="sqlite://../bakery.db?mode=rwc" cd migration cargo run

迁移工程包含 7 个迁移文件(migration/src),从建表到播种依次执行:

  • m20230101_000001_create_bakery_table.rs:创建bakery表;
  • m20230101_000002_create_baker_table.rs:创建baker表;
  • m20230101_000003_create_cake_table.rs:创建cake表;
  • m20230101_000004_create_cake_baker_table.rs:创建多对多关联表cake_baker
  • m20230101_000005_create_customer_table.rs:创建customer表;
  • m20230101_000006_create_order_table.rs:创建order表;
  • m20230101_000007_create_lineitem_table.rs:创建lineitem表;
  • m20230102_000001_seed_bakery_data.rs:向表中写入 SeaSide Bakery、LakeSide Bakery、Alice、Bob 及各款蛋糕等演示数据。

建表迁移使用 SeaORM Migration 的声明式 API,例如创建bakery表(m20230101_000001_create_bakery_table.rs):

manager .create_table( Table::create() .table("bakery") .col(pk_auto("id")) .col(string("name")) .col(double("profit_margin")) .to_owned(), ) .await

种子迁移则通过 ActiveModel 插入数据(m20230102_000001_seed_bakery_data.rs):

let bakery = bakery::ActiveModel { name: Set("SeaSide Bakery".to_owned()), profit_margin: Set(10.4), ..Default::default() }; let sea = Bakery::insert(bakery).exec(db).await?.last_insert_id;

迁移 CLI 还支持常用子命令,方便管理 Schema 演进:

命令作用
cargo run/cargo run -- up应用所有待执行迁移
cargo run -- up -n 10仅应用前 10 个迁移
cargo run -- down回滚最近一次迁移
cargo run -- down -n 10回滚最近 10 次迁移
cargo run -- fresh先删库重建,再全部重跑
cargo run -- refresh回滚全部后重新应用全部迁移
cargo run -- reset回滚全部迁移
cargo run -- status查看各迁移执行状态

第二步:安装两条 CLI 工具链

SeaORM + Seaography 的代码生成依赖两个 CLI,建议使用 2.0 系列的预发布版本:

cargo install sea-orm-cli@^2.0.0-rc cargo install seaography-cli@^2.0.0-rc
  • sea-orm-cli负责从数据库反向生成 SeaORM 实体代码;
  • seaography-cli负责基于已生成的实体,搭建完整的 GraphQL 服务工程(axum 框架 + async-graphql)。

第三步:生成实体与 GraphQL 工程

rm -rf graphql # this entire folder is generated mkdir graphql cd graphql sea-orm-cli generate entity --output-dir ./src/entities --entity-format dense --seaography seaography-cli -o . -e ./src/entities --framework axum sea-orm-seaography-example

命令逐条说明:

  • sea-orm-cli generate entity:连接DATABASE_URL指向的数据库并生成实体;--output-dir ./src/entities指定实体输出目录,--entity-format dense采用紧凑的实体代码风格,--seaography是关键开关,它让生成的实体额外携带 Seaography 所需的元数据与关系定义;
  • seaography-cli -o . -e ./src/entities --framework axum sea-orm-seaography-example:以实体目录为输入,生成名为sea-orm-seaography-example的 GraphQL 工程到当前目录,Web 框架选择 axum。

生成的工程结构与仓库中 graphql 目录一致,包括:

  • src/entities/:从数据库反向生成的 SeaORM 实体(baker、bakery、cake、cake_baker 等,含mod.rsprelude.rs);
  • src/query_root.rs:Schema 构建逻辑,负责把全部实体注册进 GraphQL 并配置深度/复杂度限制;
  • src/main.rs:axum 服务入口,暴露 Playground 与 GraphQL 端点;
  • Cargo.toml:依赖配置,其中sea-orm开启seaographyfeature,seaography按需开启graphql-playgroundwith-decimalwith-chrono等特性。

query_root.rs是理解生成代码的关键(query_root.rs):

pub fn schema_builder( context: &'static BuilderContext, database: DatabaseConnection, depth: Option<usize>, complexity: Option<usize>, ) -> SchemaBuilder { let mut builder = Builder::new(context, database.clone()); builder = register_entity_modules(builder); builder .set_depth_limit(depth) .set_complexity_limit(complexity) .schema_builder() .data(database) }

从源码结构可以推断:Builder遍历所有注册的实体模块,把每个实体的查询入口、过滤参数、分页与排序参数以及实体间关系统一翻译成 async-graphql 的动态 Schema;set_depth_limitset_complexity_limit则把上文的环境变量注入查询保护策略。

第四步:在自己的工程中调整依赖

生成后的Cargo.toml依赖大致如下(版本以当前仓库为准):

[dependencies.sea-orm] features = ["sqlx-sqlite", "runtime-tokio-native-tls", "seaography"] version = "~2.0.3" [dependencies.seaography] features = ["graphql-playground", "with-decimal", "with-chrono"] version = "~2.0.0-rc.3" [dependencies] async-graphql-axum = { version = "7.0" } axum = { version = "0.8" } dotenv = "0.15.0" tokio = { version = "1.29.1", features = ["macros", "rt-multi-thread"] }

几点实战提示:

  • sea-orm必须开启seaographyfeature,否则query_root.rs依赖的实体注册 API 不可用;
  • 数据库驱动 feature 要与实际使用的数据库匹配,示例中使用 SQLite(sqlx-sqlite),MySQL/PostgreSQL 项目需相应替换;
  • 若实体包含Decimal或时间类型字段,建议开启with-decimalwith-chrono,保证 GraphQL 标量序列化正确(本示例的cake.price正是 Decimal 类型,查询结果中价格以字符串形式返回,如"10.25");
  • 示例中[patch.crates-io] sea-orm = { path = "../../.." }用于在仓库内联调 SeaORM 本体,自己新建工程时应删除该行,直接使用 crates.io 发布的版本。

验证与测试

生成工程自带集成测试,可直接验证 GraphQL Schema 与查询结果是否符合预期:

cd graphql DATABASE_URL="sqlite://../bakery.db" cargo test

测试用例位于 query_tests.rs,覆盖了文档中的典型查询:

  • test_cake_with_bakery:按name.contains("Chocolate")过滤并嵌套取面包店信息;
  • test_cake_with_baker:按关联面包师姓名过滤;
  • 以及多级嵌套与排序分页场景。

测试内部通过sea_orm::Database::connect连接数据库,再调用query_root::schema构建 Schema 后直接executeGraphQL 请求,将返回结果与期望 JSON 逐字段比对。这套测试结构可以直接复制到自己的工程中,作为 GraphQL API 的回归保障。

小结

通过本示例可以清晰看到 SeaORM 生态中"数据访问层 + GraphQL 层"的完整协同方式:SeaORM 负责用 Rust 类型安全地描述数据库表与关系,sea-orm-cli将数据库结构反向生成为实体,seaography-cli再把实体工程升级为开箱即用的 GraphQL 服务。整条链路从bakery.db出发,几分钟内即可得到支持过滤、排序、分页与多级嵌套查询的 GraphQL API,非常适合作为"Rust 全栈 + GraphQL"项目的起步模板。后续可在此基础上继续扩展:更换 MySQL/PostgreSQL 驱动、接入认证授权中间件,或调整DEPTH_LIMIT/COMPLEXITY_LIMIT以适配更复杂的查询场景。

  • 后端
  • 数据库
  • ORM

【免费下载链接】sea-orm

🐚 A powerful relational ORM for Rust

项目地址:https://gitcode.com/gh_mirrors/se/sea-orm
点击查看免费下载

相关推荐

上一篇:AutoSubs终极指南:如何在本地设备上实现专业级AI字幕生成
下一篇:OpenDesign Airtable 设计系统深度解析:从 DESIGN 规范到 tokens.css 的落地实现

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询