ITADN
SeaQL/sea-orm
SeaQL/sea-orm · 文件 下载 ZIP
文件最后提交记录最后更新时间
README.md
以下内容由 AI 翻译,如有问题请点此提交 issue 反馈
SeaORM

SeaORM 是一个用于在 Rust 中构建 Web 服务的强大 ORM

crate build status GitHub stars
用 ⭐ 支持我们!

🐚 SeaORM

中文文档

高级关系

以高层、概念化的方式建模 1-1、1-N、M-N 甚至自引用等复杂关系。

熟悉的概念

受 Ruby、Python 和 Node.js 生态系统中流行 ORM 的启发,SeaORM 提供了一种令人倍感亲切的开发者体验。

功能丰富

SeaORM 是一个功能完备的 ORM,内置过滤器、分页和嵌套查询,可加速构建 REST、GraphQL 和 gRPC API。

生产就绪

凭借每周超过 25 万次的下载量,SeaORM 已具备生产就绪能力,受到全球初创企业和大型企业的信赖。

快速入门

Discord 加入我们的 Discord 服务器与其他人交流!

集成示例:

如果你想要一个简单、干净且能容纳在单个文件中、展示 SeaORM 最佳实践的示例,可以尝试:

让我们快速浏览一下 SeaORM 的独特功能。

表达力强的实体格式

你不必手动编写这些!实体文件可以使用 sea-orm-cli 从现有数据库生成, 以下是使用 --entity-format dense (2.0 版本新增) 生成的内容。

mod user {
    use sea_orm::entity::prelude::*;

    #[sea_orm::model]
    #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
    #[sea_orm(table_name = "user")]
    pub struct Model {
        #[sea_orm(primary_key)]
        pub id: i32,
        pub name: String,
        #[sea_orm(unique)]
        pub email: String,
        #[sea_orm(has_one)]
        pub profile: HasOne<super::profile::Entity>,
        #[sea_orm(has_many)]
        pub posts: HasMany<super::post::Entity>,
    }
}
mod post {
    use sea_orm::entity::prelude::*;

    #[sea_orm::model]
    #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
    #[sea_orm(table_name = "post")]
    pub struct Model {
        #[sea_orm(primary_key)]
        pub id: i32,
        pub user_id: i32,
        pub title: String,
        #[sea_orm(belongs_to, from = "user_id", to = "id")]
        pub author: BelongsTo<super::user::Entity>,
        #[sea_orm(has_many, via = "post_tag")] // M-N relation with junction
        pub tags: HasMany<super::tag::Entity>,
    }
}

智能实体加载器

实体加载器智能地对 1-1 关系使用 join,对 1-N 关系使用数据加载器, 即使在执行嵌套查询时也能消除 N+1 问题。

// join paths:
// user -> profile
// user -> post
//         post -> post_tag -> tag
let smart_user = user::Entity::load()
    .filter_by_id(42) // shorthand for .filter(user::COLUMN.id.eq(42))
    .with(profile::Entity) // 1-1 uses join
    .with((post::Entity, tag::Entity)) // 1-N uses data loader
    .one(db)
    .await?
    .unwrap();

// 3 queries are executed under the hood:
// 1. SELECT FROM user JOIN profile WHERE id = $
// 2. SELECT FROM post WHERE user_id IN (..)
// 3. SELECT FROM tag JOIN post_tag WHERE post_id IN (..)

smart_user
    == user::ModelEx {
        id: 42,
        name: "Bob".into(),
        email: "bob@sea-ql.org".into(),
        profile: HasOne::loaded(Some(profile::ModelEx {
            picture: "image.jpg".into(),
        })),
        posts: HasMany::Loaded(vec![post::ModelEx {
            title: "Nice weather".into(),
            tags: HasMany::Loaded(vec![tag::ModelEx {
                tag: "sunny".into(),
            }]),
        }]),
    };

ActiveModel:让嵌套持久化变得简单

使用流畅的构建器 API,在单次操作中持久化整个对象图:用户、个人资料(1-1)、帖子(1-N)和标签(M-N)。 SeaORM 会自动确定依赖关系,并以正确的顺序插入或删除对象。 这需要 SeaORM 2.0 的密集实体格式。

// this creates the nested object as shown above:
let user = user::ActiveModel::builder()
    .set_name("Bob")
    .set_email("bob@sea-ql.org")
    .set_profile(profile::ActiveModel::builder().set_picture("image.jpg"))
    .add_post(
        post::ActiveModel::builder()
            .set_title("Nice weather")
            .add_tag(tag::ActiveModel::builder().set_tag("sunny")),
    )
    .save(db)
    .await?;

模式优先还是实体优先?由你选择

SeaORM 提供了一个强大的迁移系统,让你可以轻松地创建表、修改模式并填充数据。

在 SeaORM 2.0 中,你还可以获得一流的 Entity First Workflow: 只需定义新实体或为现有实体添加列, SeaORM 将自动检测这些更改并创建新的表、列、唯一键和外键。

// SeaORM resolves foreign key dependencies and creates the tables in topological order.
// Requires the `entity-registry` and `schema-sync` feature flags.
db.get_schema_registry("my_crate::entity::*").sync(db).await;

人体工学原生 SQL

让 SeaORM 处理你 95% 的事务性查询。 对于剩余那些过于复杂而无法表达的情况, SeaORM 仍然提供了便捷的原始 SQL 编写支持。

let user = Item { name: "Bob" }; // nested parameter access
let ids = [2, 3, 4]; // expanded by the `..` operator

let user: Option<user::Model> = user::Entity::find()
    .from_raw_sql(raw_sql!(
        Sqlite,
        r#"SELECT "id", "name" FROM "user"
           WHERE "name" LIKE {user.name}
           AND "id" in ({..ids})
        "#
    ))
    .one(db)
    .await?;

同步支持

sea-orm-sync 提供了完整的 SeaORM API,无需异步运行时,使其非常适合使用 SQLite 的轻量级 CLI 程序。

请参阅 快速入门示例 了解用法。

基础

查询

SeaORM 在 Entity 层面建模 1-N 和 M-N 关系, 让你能够通过连接表在单次调用中遍历多对多链接。

// find all models
let cakes: Vec<cake::Model> = Cake::find().all(db).await?;

// find and filter
let chocolate: Vec<cake::Model> = Cake::find()
    .filter(Cake::COLUMN.name.contains("chocolate"))
    .all(db)
    .await?;

// find one model
let cheese: Option<cake::Model> = Cake::find_by_id(1).one(db).await?;
let cheese: cake::Model = cheese.unwrap();

// find related models (lazy)
let fruit: Option<fruit::Model> = cheese.find_related(Fruit).one(db).await?;

// find related models (eager): for 1-1 relations
let cake_with_fruit: Vec<(cake::Model, Option<fruit::Model>)> =
    Cake::find().find_also_related(Fruit).all(db).await?;

// find related models (eager): works for both 1-N and M-N relations
let cake_with_fillings: Vec<(cake::Model, Vec<filling::Model>)> = Cake::find()
    .find_with_related(Filling) // for M-N relations, two joins are performed
    .all(db) // rows are automatically consolidated by left entity
    .await?;

嵌套查询

部分模型通过让你仅查询所需的字段来防止过度获取; 它还使编写深度嵌套的关系查询变得简单。

use sea_orm::DerivePartialModel;

#[derive(DerivePartialModel)]
#[sea_orm(entity = "cake::Entity")]
struct CakeWithFruit {
    id: i32,
    name: String,
    #[sea_orm(nested)]
    fruit: Option<fruit::Model>, // this can be a regular or another partial model
}

let cakes: Vec<CakeWithFruit> = Cake::find()
    .left_join(fruit::Entity) // no need to specify join condition
    .into_partial_model() // only the columns in the partial model will be selected
    .all(db)
    .await?;

插入

SeaORM 的 ActiveModel 让你可以直接使用 Rust 数据结构, 并通过简单的 API 进行持久化。 从不同的数据源插入大批量行非常容易。

let apple = fruit::ActiveModel {
    name: Set("Apple".to_owned()),
    ..Default::default() // no need to set primary key
};

let pear = fruit::ActiveModel {
    name: Set("Pear".to_owned()),
    ..Default::default()
};

// insert one: Active Record style
let apple = apple.insert(db).await?;
apple.id == 1;

// insert one: repository style
let result = Fruit::insert(apple).exec(db).await?;
result.last_insert_id == 1;

// insert many returning last insert id
let result = Fruit::insert_many([apple, pear]).exec(db).await?;
result.last_insert_id == Some(2);

插入(高级)

你可以利用数据库特定功能来执行 upsert 和幂等插入。

// insert many with returning (if supported by database)
let models: Vec<fruit::Model> = Fruit::insert_many([apple, pear])
    .exec_with_returning(db)
    .await?;
models[0]
    == fruit::Model {
        id: 1, // database assigned value
        name: "Apple".to_owned(),
        cake_id: None,
    };

// insert with ON CONFLICT on primary key do nothing, with MySQL specific polyfill
let result = Fruit::insert_many([apple, pear])
    .on_conflict_do_nothing()
    .exec(db)
    .await?;

matches!(result, TryInsertResult::Conflicted);

更新

ActiveModel 通过仅更新你已修改的字段来避免竞态条件, 绝不覆盖未触及的列。 你还可以使用流畅的查询构建 API 来编写复杂的批量更新查询。

use sea_orm::sea_query::{Expr, Value};

let pear: Option<fruit::Model> = Fruit::find_by_id(1).one(db).await?;
let mut pear: fruit::ActiveModel = pear.unwrap().into();

pear.name = Set("Sweet pear".to_owned()); // update value of a single field

// update one: only changed columns will be updated
let pear: fruit::Model = pear.update(db).await?;

// update many: UPDATE "fruit" SET "cake_id" = "cake_id" + 2
//               WHERE "fruit"."name" LIKE '%Apple%'
Fruit::update_many()
    .col_expr(fruit::COLUMN.cake_id, fruit::COLUMN.cake_id.add(2))
    .filter(fruit::COLUMN.name.contains("Apple"))
    .exec(db)
    .await?;

保存

你可以使用 ActiveModel 执行“插入或更新”操作,从而轻松组合事务性操作。

let banana = fruit::ActiveModel {
    id: NotSet,
    name: Set("Banana".to_owned()),
    ..Default::default()
};

// create, because primary key `id` is `NotSet`
let mut banana = banana.save(db).await?;

banana.id == Unchanged(2);
banana.name = Set("Banana Mongo".to_owned());

// update, because primary key `id` is present
let banana = banana.save(db).await?;

删除

与插入和更新保持一致的相同 ActiveModel API。

// delete one: Active Record style
let orange: Option<fruit::Model> = Fruit::find_by_id(1).one(db).await?;
let orange: fruit::Model = orange.unwrap();
orange.delete(db).await?;

// delete one: repository style
let orange = fruit::ActiveModel {
    id: Set(2),
    ..Default::default()
};
fruit::Entity::delete(orange).exec(db).await?;

// delete many: DELETE FROM "fruit" WHERE "fruit"."name" LIKE '%Orange%'
fruit::Entity::delete_many()
    .filter(fruit::COLUMN.name.contains("Orange"))
    .exec(db)
    .await?;

原始 SQL 查询

raw_sql! 宏类似于 format! 宏,但没有 SQL 注入的风险。 它支持嵌套参数插值、数组和元组展开,甚至支持重复组, 在构建复杂查询时提供了极大的灵活性。

#[derive(FromQueryResult)]
struct CakeWithBakery {
    name: String,
    #[sea_orm(nested)]
    bakery: Option<Bakery>,
}

#[derive(FromQueryResult)]
struct Bakery {
    #[sea_orm(alias = "bakery_name")]
    name: String,
}

let cake_ids = [2, 3, 4]; // expanded by the `..` operator

// can use many APIs with raw SQL, including nested select
let cake: Option<CakeWithBakery> = CakeWithBakery::find_by_statement(raw_sql!(
    Sqlite,
    r#"SELECT "cake"."name", "bakery"."name" AS "bakery_name"
       FROM "cake"
       LEFT JOIN "bakery" ON "cake"."bakery_id" = "bakery"."id"
       WHERE "cake"."id" IN ({..cake_ids})"#
))
.one(db)
.await?;

🧭 Seaography: instant GraphQL API

Seaography 是一个为 SeaORM 构建的 GraphQL 框架。 Seaography 允许你快速构建 GraphQL resolvers。 只需几条命令,你就可以从 SeaORM entities 启动一个功能齐全的 GraphQL server, 包含 filter、pagination、relational queries 和 mutations!

查看 Seaography Example 以了解更多信息。

🖥️ SeaORM Pro: 专业管理面板

SeaORM Pro 是一个管理面板解决方案,允许您快速轻松地为您的应用启动一个管理面板 - 无需前端开发技能,但当然有会更好!

SeaORM Pro 已更新以支持 SeaORM 2.0 中的最新功能。

特性:

  • 完整的 CRUD
  • 基于 React + GraphQL 构建
  • 内置 GraphQL resolver
  • 使用 TOML 配置自定义 UI
  • 基于角色的访问控制 (2.0 新增)

阅读 快速入门 指南以了解更多信息。

SQL Server 支持

SQL Server for SeaORM 为 MSSQL 提供与 SeaORM 相同的 API。我们移植了所有测试用例和示例,并补充了 MSSQL 特定的文档。如果您正在开发企业级软件,可以申请商业访问权限。目前基于 SeaORM 1.0,但当 SeaORM 2.0 最终确定时,我们将为现有用户提供免费升级。

发布

SeaORM 2.0 已进入发布候选阶段。我们非常希望您能试用它,并通过分享您的反馈来帮助塑造最终版本。

SeaORM 2.0 正在成为我们迄今为止最重要的版本——包含一些破坏性变更、大量增强功能,并明确聚焦于开发者体验。

如果您大量使用 SeaQuery,我们建议查看我们关于 SeaQuery 1.0 发布的博文:

许可证

在以下任一许可证下授权

at your option.

Contribution

除非您明确声明其他情况,否则您有意提交以纳入该作品的任何贡献,如 Apache-2.0 许可证中所定义,均将按上述方式双重授权,不附加任何额外条款或条件。

我们邀请您参与、贡献,并共同帮助构建 Rust 的未来。

向我们的贡献者致以诚挚的感谢!

Contributors

谁在使用 SeaORM?

以下是一些使用 SeaORM 构建的优秀开源软件的简短列表。欢迎提交你的项目

项目GitHub标语
ZedGitHub stars一款高性能、多人协作的代码编辑器
ServoGitHub starsServo 并行浏览器引擎项目
OpenObserveGitHub stars开源可观测性平台
RisingWaveGitHub stars流处理与管理平台
WarpgateGitHub stars可与任何 SSH 客户端配合使用的智能 SSH 堡垒机
LLDAPGitHub stars用于用户管理的轻量级 LDAP 服务器
SvixGitHub stars企业级 Webhooks 服务
RyotGitHub stars你唯一需要的自托管追踪器
OctoBaseGitHub stars轻量级、可扩展、离线协作数据后端
System InitiativeGitHub starsDevOps 自动化平台

赞助

SeaQL.org 是一个由充满热情的开发者运营的独立开源组织。 如果你愿意慷慨解囊,通过 GitHub Sponsor 进行的小额捐赠将不胜感激,并对维持该组织的运营大有裨益。

银牌赞助商

感谢我们的银牌赞助商:Digital Ocean,赞助了我们的服务器。以及 JetBrains,赞助了我们的 IDE。

吉祥物

Ferris 的朋友,寄居蟹 Terres 是 SeaORM 的官方吉祥物。他的爱好是收集贝壳。

Terres

🦀 Rustacean 贴纸包

Rustacean 贴纸包是表达你对 Rust 热爱的完美方式。我们的贴纸采用优质防水乙烯基材料制成,具有独特的哑光质感。

贴纸包内容:

  • SeaQL 项目的标志:SeaQL、SeaORM、SeaQuery、Seaography
  • 吉祥物:螃蟹 Ferris x 3、寄居蟹 Terres
  • Rustacean 字标

支持 SeaQL 并获取贴纸包! 所有收益将直接用于 SeaQL 项目的持续开发。

Rustacean Sticker Pack by SeaQL