不灭的焱

革命尚未成功,同志仍须努力 下载Java21

作者:AlbertWen  添加时间:2026-07-08 23:54:52  修改时间:2026-07-24 23:00:53  分类:01.Rust编程  编辑

目录

下面给你一份 SeaORM 入门到可落地使用教程,以 Rust + SeaORM + MySQL8.0 为主线。

先说版本选择:SeaORM 官方站当前默认展示 2.0.x 文档,但 docs.rs 上 sea_orm 的 latest 仍显示 1.1.20;同时 SeaORM 1.1.x 官方文档页提示该版本“不再积极维护”,建议看 2.0.x。为了你现在做 Rust Web 项目更稳,我下面用 SeaORM 1.1.x 稳定写法讲,后续升级 2.0 时再单独处理差异。(Docs.rs)

一、SeaORM 是什么?

SeaORM 是 Rust 的异步 ORM 框架,主要用于把 Rust 代码和关系型数据库连接起来。官方定位是面向 Web 服务的关系型 ORM,支持复杂动态查询、过滤、分页、关联查询等能力。SeaORM 当前常见支持 MySQL、PostgreSQL、SQLite 等数据库。(Docs.rs)

你可以简单理解为:

SeaORM = Rust 里的 MyBatis-Plus / JPA / TypeORM 类似角色

它帮你做:

Rust Struct  <------>  数据库表
查询条件     <------>  SQL WHERE
分页排序     <------>  LIMIT / OFFSET / ORDER BY
关联关系     <------>  JOIN / 关联加载
事务         <------>  BEGIN / COMMIT / ROLLBACK

二、SeaORM 核心概念

SeaORM 里最重要的是这几个概念:

概念 作用 类比
Entity 表对象,代表一张表 Java 里的 Mapper / Repository 的一部分
Model 查询出来的一行数据 POJO / DTO
ActiveModel 用于新增、修改的数据模型 可写入版本的 Model
Column 表字段枚举 字段名
Relation 表关系 一对一、一对多、多对多
ActiveValue 表示字段状态:Set / NotSet / Unchanged 哪些字段要写入数据库

SeaORM 官方文档也明确说明:查询出来的数据行对应 Model;插入和更新主要依赖 ActiveModel,而 ActiveModel 的字段会被 ActiveValue 包起来,用于表达字段是否被设置。(SeaQL)

三、推荐学习路线

我建议按这个顺序学:

1. 连接数据库
2. 通过 migration 建表
3. 通过 sea-orm-cli 生成 Entity
4. 写查询 SELECT
5. 写新增 INSERT
6. 写修改 UPDATE
7. 写删除 DELETE
8. 写分页和排序
9. 写关联查询
10. 写事务
11. 封装到项目分层结构里

SeaORM 官方推荐 schema-first 流程:先写 migration 建数据库结构,然后从实际数据库生成 Entity。SeaORM 的 migration 里通常包含 updown 两个方法,up 用于创建表、字段、索引等,down 用于回滚。(SeaQL)

四、创建项目

cargo new seaorm-demo
cd seaorm-demo

项目结构先用这个:

seaorm-demo/
├── Cargo.toml
├── .env
├── migration/
└── src/
    ├── main.rs
    ├── db.rs
    ├── entity/
    └── service/
        └── user_service.rs

五、准备 MySQL 数据库

假设你本地 MySQL8.0 信息如下:

host: 127.0.0.1
port: 3306
user: root
password: 123456
database: seaorm_demo

创建数据库:

CREATE DATABASE seaorm_demo
DEFAULT CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;

项目根目录创建 .env

DATABASE_URL=mysql://root:123456@127.0.0.1:3306/seaorm_demo

SeaORM CLI 支持从 .env 或环境变量读取 DATABASE_URL,也可以通过 -u / --database-url 显式传入数据库连接。(SeaQL)

六、配置 Cargo.toml

[package]
name = "seaorm-demo"
version = "0.1.0"
edition = "2024"

[dependencies]
tokio = { version = "1", features = ["full"] }

sea-orm = { version = "1.1", features = [
    "sqlx-mysql",
    "runtime-tokio-rustls",
    "macros",
    "with-chrono",
    "with-json"
] }

dotenvy = "0.15"
anyhow = "1"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
chrono = { version = "0.4", features = ["serde"] }

说明:

sqlx-mysql              启用 MySQL 驱动
runtime-tokio-rustls    使用 tokio 异步运行时
macros                  启用 SeaORM 派生宏
with-chrono             支持 chrono 时间类型
with-json               支持 JSON 字段

SeaORM 是基于 SQLx 和 SeaQuery 构建的异步、动态 ORM,这也是它能支持异步查询和动态 SQL 构造的重要原因。(Docs.rs)

七、安装 sea-orm-cli

cargo install sea-orm-cli --version 1.1.20

或者:

cargo install sea-orm-cli@1.1.20

SeaORM CLI 可以用于初始化 migration、生成 migration 文件、从数据库表生成 Entity。官方 1.1.x 文档也说明,可以用 sea-orm-cli migrate init 初始化迁移目录,用 sea-orm-cli generate entity 从数据库发现表并生成 Entity 文件。(SeaQL)

八、初始化 Migration

sea-orm-cli migrate init

执行后会生成:

migration/
├── Cargo.toml
├── README.md
└── src/
    ├── lib.rs
    ├── main.rs
    └── m20220101_000001_create_table.rs

官方文档说明,SeaORM migration 会自动创建一张迁移记录表,默认名为 seaql_migrations,用于记录哪些 migration 已经执行过。(SeaQL)

九、修改 migration/Cargo.toml

打开 migration/Cargo.toml,配置成 MySQL:

[package]
name = "migration"
version = "0.1.0"
edition = "2024"
publish = false

[lib]
name = "migration"
path = "src/lib.rs"

[dependencies]
async-std = { version = "1", features = ["attributes", "tokio1"] }

[dependencies.sea-orm-migration]
version = "1.1"
features = [
    "runtime-tokio-rustls",
    "sqlx-mysql"
]

十、创建用户表 Migration

生成新的 migration 文件:

sea-orm-cli migrate generate create_user_table

会生成类似:

migration/src/m20260708_000001_create_user_table.rs

把内容改成:

use sea_orm_migration::prelude::*;

#[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(User::Table)
                    .if_not_exists()
                    .col(
                        ColumnDef::new(User::Id)
                            .big_integer()
                            .not_null()
                            .auto_increment()
                            .primary_key(),
                    )
                    .col(
                        ColumnDef::new(User::Username)
                            .string_len(64)
                            .not_null()
                            .unique_key(),
                    )
                    .col(
                        ColumnDef::new(User::PasswordHash)
                            .string_len(255)
                            .not_null(),
                    )
                    .col(
                        ColumnDef::new(User::Nickname)
                            .string_len(64)
                            .null(),
                    )
                    .col(
                        ColumnDef::new(User::Status)
                            .integer()
                            .not_null()
                            .default(1),
                    )
                    .col(
                        ColumnDef::new(User::CreatedAt)
                            .timestamp()
                            .not_null()
                            .default(Expr::current_timestamp()),
                    )
                    .col(
                        ColumnDef::new(User::UpdatedAt)
                            .timestamp()
                            .not_null()
                            .default(Expr::current_timestamp()),
                    )
                    .to_owned(),
            )
            .await
    }

    async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
        manager
            .drop_table(Table::drop().table(User::Table).to_owned())
            .await
    }
}

#[derive(DeriveIden)]
enum User {
    Table,
    Id,
    Username,
    PasswordHash,
    Nickname,
    Status,
    CreatedAt,
    UpdatedAt,
}

然后确认 migration/src/lib.rs 里包含新 migration:

pub use sea_orm_migration::prelude::*;

mod m20260708_000001_create_user_table;

pub struct Migrator;

#[async_trait::async_trait]
impl MigratorTrait for Migrator {
    fn migrations() -> Vec<Box<dyn MigrationTrait>> {
        vec![Box::new(m20260708_000001_create_user_table::Migration)]
    }
}

十一、执行 Migration

sea-orm-cli migrate up

查看数据库:

SHOW TABLES;
DESC user;

应该能看到:

user
seaql_migrations

十二、生成 Entity

执行:

sea-orm-cli generate entity \
  -u mysql://root:123456@127.0.0.1:3306/seaorm_demo \
  -o src/entity \
  --with-serde both \
  -l

参数说明:

-u / --database-url    数据库连接
-o / --output-dir      Entity 输出目录
--with-serde both      自动派生 Serialize / Deserialize
-l                    生成 lib.rs,而不是 mod.rs

官方文档说明,sea-orm-cli generate entity 会发现数据库中的表,并为每张表生成对应的 Entity;它支持 MySQL、PostgreSQL、SQLite。-l / --lib 可以生成 lib.rs 而不是 mod.rs,这点适合你之前偏好的“不使用 mod.rs”的项目风格。(SeaQL)

生成后大概是:

src/entity/
├── lib.rs
├── prelude.rs
└── user.rs

十三、main.rs 引入 entity

因为我们把 entity 放在 src/entity/lib.rs,在普通二进制项目里,可以这样处理。

src/main.rs

mod db;
mod entity;

use anyhow::Result;
use sea_orm::EntityTrait;

use crate::entity::prelude::User;

#[tokio::main]
async fn main() -> Result<()> {
    let db = db::connect().await?;

    let users = User::find().all(&db).await?;

    println!("users = {:?}", users);

    Ok(())
}

十四、封装数据库连接

src/db.rs

use anyhow::Result;
use dotenvy::dotenv;
use sea_orm::{ConnectOptions, Database, DatabaseConnection};
use std::env;
use std::time::Duration;

pub async fn connect() -> Result<DatabaseConnection> {
    dotenv().ok();

    let database_url = env::var("DATABASE_URL")?;

    let mut opt = ConnectOptions::new(database_url);

    opt.max_connections(20)
        .min_connections(5)
        .connect_timeout(Duration::from_secs(8))
        .acquire_timeout(Duration::from_secs(8))
        .idle_timeout(Duration::from_secs(300))
        .sqlx_logging(true);

    let db = Database::connect(opt).await?;

    Ok(db)
}

十五、第一个查询:查询全部用户

use anyhow::Result;
use sea_orm::EntityTrait;

use crate::entity::prelude::User;
use crate::entity::user;

pub async fn list_users(db: &sea_orm::DatabaseConnection) -> Result<Vec<user::Model>> {
    let users = User::find().all(db).await?;
    Ok(users)
}

SeaORM 的 find() 会返回查询构造器,可以继续追加过滤、排序、分页等条件。官方文档也说明,find_by_id 可以按主键查单条,find 可以构造 where、order by 等常见查询表达式。(SeaQL)

十六、按 ID 查询

use anyhow::Result;
use sea_orm::EntityTrait;

use crate::entity::prelude::User;
use crate::entity::user;

pub async fn get_user_by_id(
    db: &sea_orm::DatabaseConnection,
    id: i64,
) -> Result<Option<user::Model>> {
    let user = User::find_by_id(id).one(db).await?;
    Ok(user)
}

说明:

find_by_id(id)   根据主键查
one(db)          返回 Option<Model>
all(db)          返回 Vec<Model>

十七、条件查询

use anyhow::Result;
use sea_orm::{ColumnTrait, EntityTrait, QueryFilter, QueryOrder};

use crate::entity::prelude::User;
use crate::entity::user;

pub async fn search_users(
    db: &sea_orm::DatabaseConnection,
    keyword: &str,
) -> Result<Vec<user::Model>> {
    let users = User::find()
        .filter(user::Column::Username.contains(keyword))
        .filter(user::Column::Status.eq(1))
        .order_by_desc(user::Column::Id)
        .all(db)
        .await?;

    Ok(users)
}

常见条件:

user::Column::Username.eq("admin")
user::Column::Username.ne("admin")
user::Column::Username.contains("adm")
user::Column::Id.gt(10)
user::Column::Id.gte(10)
user::Column::Id.lt(100)
user::Column::Status.is_in([1, 2])
user::Column::Nickname.is_null()
user::Column::Nickname.is_not_null()

十八、新增用户

SeaORM 新增数据时一般使用 ActiveModel

use anyhow::Result;
use sea_orm::{ActiveModelTrait, ActiveValue::Set};

use crate::entity::user;

pub async fn create_user(
    db: &sea_orm::DatabaseConnection,
    username: String,
    password_hash: String,
    nickname: Option<String>,
) -> Result<user::Model> {
    let active_user = user::ActiveModel {
        username: Set(username),
        password_hash: Set(password_hash),
        nickname: Set(nickname),
        status: Set(1),
        ..Default::default()
    };

    let user = active_user.insert(db).await?;

    Ok(user)
}

重点:

Set(value)          表示这个字段要写入
NotSet              表示这个字段不参与写入
Default::default()  其他字段默认 NotSet

官方文档也说明,插入一个 ActiveModel 后可以拿回新的 Model,自动生成字段会从数据库中取回。(SeaQL)

十九、批量新增

use anyhow::Result;
use sea_orm::{ActiveValue::Set, EntityTrait};

use crate::entity::prelude::User;
use crate::entity::user;

pub async fn batch_create_users(db: &sea_orm::DatabaseConnection) -> Result<()> {
    let users = vec![
        user::ActiveModel {
            username: Set("alice".to_string()),
            password_hash: Set("hash_1".to_string()),
            nickname: Set(Some("Alice".to_string())),
            status: Set(1),
            ..Default::default()
        },
        user::ActiveModel {
            username: Set("bob".to_string()),
            password_hash: Set("hash_2".to_string()),
            nickname: Set(Some("Bob".to_string())),
            status: Set(1),
            ..Default::default()
        },
    ];

    User::insert_many(users).exec(db).await?;

    Ok(())
}

SeaORM 官方文档提供了 insert_many 用于批量插入。(SeaQL)

二十、修改用户

先查出来,再转成 ActiveModel,然后设置要修改的字段:

use anyhow::{anyhow, Result};
use sea_orm::{ActiveModelTrait, ActiveValue::Set, EntityTrait};

use crate::entity::prelude::User;
use crate::entity::user;

pub async fn update_nickname(
    db: &sea_orm::DatabaseConnection,
    id: i64,
    nickname: Option<String>,
) -> Result<user::Model> {
    let user = User::find_by_id(id)
        .one(db)
        .await?
        .ok_or_else(|| anyhow!("用户不存在"))?;

    let mut active_user: user::ActiveModel = user.into();

    active_user.nickname = Set(nickname);

    let updated = active_user.update(db).await?;

    Ok(updated)
}

官方文档说明:查询得到的是 Model,要保存回数据库,需要先转成 ActiveModel;生成的 UPDATE SQL 默认只包含 Set 的字段。(SeaQL)

二十一、批量修改

use anyhow::Result;
use sea_orm::{ColumnTrait, EntityTrait, QueryFilter};

use crate::entity::prelude::User;
use crate::entity::user;

pub async fn disable_user(
    db: &sea_orm::DatabaseConnection,
    id: i64,
) -> Result<()> {
    User::update_many()
        .col_expr(user::Column::Status, sea_orm::sea_query::Expr::value(0))
        .filter(user::Column::Id.eq(id))
        .exec(db)
        .await?;

    Ok(())
}

官方文档说明,update_many() 可以不先查询每个 Model,直接对多行数据执行更新。(SeaQL)

二十二、删除用户

物理删除:

use anyhow::Result;
use sea_orm::{EntityTrait, ModelTrait};

use crate::entity::prelude::User;

pub async fn delete_user(
    db: &sea_orm::DatabaseConnection,
    id: i64,
) -> Result<()> {
    if let Some(user) = User::find_by_id(id).one(db).await? {
        user.delete(db).await?;
    }

    Ok(())
}

业务系统中更推荐软删除:

User::update_many()
    .col_expr(user::Column::Status, sea_orm::sea_query::Expr::value(0))
    .filter(user::Column::Id.eq(id))
    .exec(db)
    .await?;

二十三、分页查询

use anyhow::Result;
use sea_orm::{ColumnTrait, EntityTrait, PaginatorTrait, QueryFilter, QueryOrder};

use crate::entity::prelude::User;
use crate::entity::user;

pub struct PageResult<T> {
    pub page: u64,
    pub page_size: u64,
    pub total: u64,
    pub data: Vec<T>,
}

pub async fn page_users(
    db: &sea_orm::DatabaseConnection,
    page: u64,
    page_size: u64,
    keyword: Option<String>,
) -> Result<PageResult<user::Model>> {
    let mut query = User::find()
        .filter(user::Column::Status.eq(1))
        .order_by_desc(user::Column::Id);

    if let Some(keyword) = keyword {
        if !keyword.trim().is_empty() {
            query = query.filter(user::Column::Username.contains(keyword));
        }
    }

    let paginator = query.paginate(db, page_size);

    let total = paginator.num_items().await?;
    let data = paginator.fetch_page(page.saturating_sub(1)).await?;

    Ok(PageResult {
        page,
        page_size,
        total,
        data,
    })
}

注意:

前端 page = 1
SeaORM fetch_page = 0
所以要 page - 1

SeaORM 的官方文档也提供了 paginate(db, page_size) 这种分页方式。(SeaQL)

二十四、事务

比如:创建用户后,再创建用户角色关系。两步必须同时成功或同时失败。

use anyhow::Result;
use sea_orm::{
    ActiveModelTrait,
    ActiveValue::Set,
    DatabaseConnection,
    EntityTrait,
    TransactionTrait,
};

use crate::entity::user;

pub async fn create_user_in_txn(
    db: &DatabaseConnection,
    username: String,
    password_hash: String,
) -> Result<user::Model> {
    let result = db
        .transaction::<_, user::Model, sea_orm::DbErr>(|txn| {
            Box::pin(async move {
                let active_user = user::ActiveModel {
                    username: Set(username),
                    password_hash: Set(password_hash),
                    status: Set(1),
                    ..Default::default()
                };

                let user = active_user.insert(txn).await?;

                // 这里可以继续插入 user_role 表
                // 如果后面任何一步返回 Err,事务会回滚

                Ok(user)
            })
        })
        .await?;

    Ok(result)
}

SeaORM 提供两种事务 API:闭包方式和 begin / commit / rollback 方式;官方文档说明,闭包返回 Ok 会提交,返回 Err 会回滚。(SeaQL)

二十五、加上角色表和用户角色表

如果你后面做 RBAC,通常至少有:

sys_user
sys_role
sys_user_role
sys_menu
sys_role_menu

这里先演示用户和角色的多对多关系。

1. role 表

CREATE TABLE role (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    code VARCHAR(64) NOT NULL UNIQUE,
    name VARCHAR(64) NOT NULL,
    status INT NOT NULL DEFAULT 1,
    created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);

2. user_role 表

CREATE TABLE user_role (
    user_id BIGINT NOT NULL,
    role_id BIGINT NOT NULL,
    PRIMARY KEY (user_id, role_id)
);

然后重新生成实体:

sea-orm-cli generate entity \
  -u mysql://root:123456@127.0.0.1:3306/seaorm_demo \
  -o src/entity \
  --with-serde both \
  -l

二十六、关联查询思路

SeaORM 支持 lazy loading、eager loading、batch loading 等关联加载方式。官方文档说明,find_related 是按需加载,find_with_related 可以一次性加载并按父模型分组;对于一对多、多对多场景,find_with_related 会把关联模型按父模型分组返回。(SeaQL)

查询用户拥有的角色,大体可以这样:

use anyhow::Result;
use sea_orm::{ColumnTrait, EntityTrait, QueryFilter};

use crate::entity::{role, user_role};

pub async fn list_user_roles(
    db: &sea_orm::DatabaseConnection,
    user_id: i64,
) -> Result<Vec<role::Model>> {
    let rows = user_role::Entity::find()
        .filter(user_role::Column::UserId.eq(user_id))
        .all(db)
        .await?;

    let role_ids: Vec<i64> = rows.into_iter().map(|r| r.role_id).collect();

    let roles = role::Entity::find()
        .filter(role::Column::Id.is_in(role_ids))
        .filter(role::Column::Status.eq(1))
        .all(db)
        .await?;

    Ok(roles)
}

这个写法最容易理解,也最适合入门。熟悉后再封装 SeaORM 的 Relation。

二十七、一个完整 UserService 示例

src/service/user_service.rs

use anyhow::{anyhow, Result};
use sea_orm::{
    sea_query::Expr,
    ActiveModelTrait,
    ActiveValue::Set,
    ColumnTrait,
    DatabaseConnection,
    EntityTrait,
    PaginatorTrait,
    QueryFilter,
    QueryOrder,
};

use crate::entity::prelude::User;
use crate::entity::user;

pub struct UserService;

pub struct PageResult<T> {
    pub page: u64,
    pub page_size: u64,
    pub total: u64,
    pub data: Vec<T>,
}

impl UserService {
    pub async fn create(
        db: &DatabaseConnection,
        username: String,
        password_hash: String,
        nickname: Option<String>,
    ) -> Result<user::Model> {
        let exists = User::find()
            .filter(user::Column::Username.eq(username.clone()))
            .one(db)
            .await?;

        if exists.is_some() {
            return Err(anyhow!("用户名已存在"));
        }

        let active_user = user::ActiveModel {
            username: Set(username),
            password_hash: Set(password_hash),
            nickname: Set(nickname),
            status: Set(1),
            ..Default::default()
        };

        let user = active_user.insert(db).await?;

        Ok(user)
    }

    pub async fn get_by_id(
        db: &DatabaseConnection,
        id: i64,
    ) -> Result<Option<user::Model>> {
        let user = User::find_by_id(id).one(db).await?;
        Ok(user)
    }

    pub async fn page(
        db: &DatabaseConnection,
        page: u64,
        page_size: u64,
        keyword: Option<String>,
    ) -> Result<PageResult<user::Model>> {
        let mut query = User::find()
            .filter(user::Column::Status.eq(1))
            .order_by_desc(user::Column::Id);

        if let Some(keyword) = keyword {
            if !keyword.trim().is_empty() {
                query = query.filter(user::Column::Username.contains(keyword));
            }
        }

        let paginator = query.paginate(db, page_size);

        let total = paginator.num_items().await?;
        let data = paginator.fetch_page(page.saturating_sub(1)).await?;

        Ok(PageResult {
            page,
            page_size,
            total,
            data,
        })
    }

    pub async fn update_nickname(
        db: &DatabaseConnection,
        id: i64,
        nickname: Option<String>,
    ) -> Result<user::Model> {
        let user = User::find_by_id(id)
            .one(db)
            .await?
            .ok_or_else(|| anyhow!("用户不存在"))?;

        let mut active_user: user::ActiveModel = user.into();

        active_user.nickname = Set(nickname);

        let updated = active_user.update(db).await?;

        Ok(updated)
    }

    pub async fn disable(
        db: &DatabaseConnection,
        id: i64,
    ) -> Result<()> {
        User::update_many()
            .col_expr(user::Column::Status, Expr::value(0))
            .filter(user::Column::Id.eq(id))
            .exec(db)
            .await?;

        Ok(())
    }
}

二十八、main.rs 测试调用

mod db;
mod entity;

mod service {
    pub mod user_service;
}

use anyhow::Result;
use service::user_service::UserService;

#[tokio::main]
async fn main() -> Result<()> {
    let db = db::connect().await?;

    let user = UserService::create(
        &db,
        "admin".to_string(),
        "mock_password_hash".to_string(),
        Some("系统管理员".to_string()),
    )
    .await?;

    println!("created user: {:?}", user);

    let page = UserService::page(&db, 1, 10, None).await?;

    println!("total: {}", page.total);
    println!("data: {:?}", page.data);

    Ok(())
}

运行:

cargo run

二十九、SeaORM 常见 Trait 速查

写 SeaORM 时,经常要引入这些 Trait:

use sea_orm::EntityTrait;
use sea_orm::ColumnTrait;
use sea_orm::QueryFilter;
use sea_orm::QueryOrder;
use sea_orm::PaginatorTrait;
use sea_orm::ActiveModelTrait;
use sea_orm::ModelTrait;
use sea_orm::TransactionTrait;

作用:

Trait 常用方法
EntityTrait find, find_by_id, insert, update_many, delete_many
ColumnTrait eq, ne, gt, contains, is_in
QueryFilter filter
QueryOrder order_by_asc, order_by_desc
PaginatorTrait paginate
ActiveModelTrait insert, update, save, delete
ModelTrait delete, find_related
TransactionTrait transaction, begin

三十、SeaORM 和 SQL 的对应关系

查询全部

User::find().all(db).await?;

对应:

SELECT * FROM user;

按 ID 查询

User::find_by_id(1).one(db).await?;

对应:

SELECT * FROM user WHERE id = 1 LIMIT 1;

条件查询

User::find()
    .filter(user::Column::Username.contains("adm"))
    .filter(user::Column::Status.eq(1))
    .all(db)
    .await?;

对应:

SELECT * FROM user
WHERE username LIKE '%adm%'
AND status = 1;

排序分页

User::find()
    .order_by_desc(user::Column::Id)
    .paginate(db, 10)
    .fetch_page(0)
    .await?;

对应:

SELECT * FROM user
ORDER BY id DESC
LIMIT 10 OFFSET 0;

新增

user::ActiveModel {
    username: Set("admin".to_string()),
    password_hash: Set("xxx".to_string()),
    ..Default::default()
}
.insert(db)
.await?;

对应:

INSERT INTO user (username, password_hash)
VALUES ('admin', 'xxx');

三十一、常见坑

1. 忘记引入 Trait

报错类似:

no function or associated item named `find` found

解决:

use sea_orm::EntityTrait;

如果 filter 报错:

use sea_orm::QueryFilter;
use sea_orm::ColumnTrait;

如果 paginate 报错:

use sea_orm::PaginatorTrait;

2. ActiveModel 字段没有用 Set

错误写法:

username: "admin".to_string()

正确写法:

username: Set("admin".to_string())

因为 ActiveModel 的字段不是普通值,而是:

ActiveValue<T>

SeaORM 官方文档说明,ActiveModel 的字段由 ActiveValue 包装,Set 表示要写入,NotSet 表示不设置,Unchanged 表示未变化。(SeaQL)

3. one() 返回的是 Option

let user = User::find_by_id(id).one(db).await?;

类型是:

Option<user::Model>

要处理不存在:

let user = User::find_by_id(id)
    .one(db)
    .await?
    .ok_or_else(|| anyhow!("用户不存在"))?;

4. page 页码从 0 开始

SeaORM:

fetch_page(0)

前端一般:

第 1 页

所以:

fetch_page(page - 1)

5. 数据库表变了,Entity 没重新生成

如果你新增字段:

ALTER TABLE user ADD COLUMN email VARCHAR(128);

但是 Rust 代码里找不到 Email 字段,通常是因为你没重新生成 entity:

sea-orm-cli generate entity \
  -u mysql://root:123456@127.0.0.1:3306/seaorm_demo \
  -o src/entity \
  --with-serde both \
  -l

三十二、推荐项目落地结构

如果是你之前提到的 Rust2024 + Salvo + SeaORM + MySQL8 + Redis6 + RBAC,可以这样放:

work-order-system/
├── Cargo.toml
├── .env
├── migration/
│   ├── Cargo.toml
│   └── src/
│       ├── lib.rs
│       └── m20260708_000001_create_rbac_tables.rs
└── src/
    ├── main.rs
    ├── app.rs
    ├── config/
    │   ├── database.rs
    │   └── redis.rs
    ├── infra/
    │   ├── db.rs
    │   └── redis.rs
    ├── entity/
    │   ├── lib.rs
    │   ├── user.rs
    │   ├── role.rs
    │   ├── user_role.rs
    │   ├── menu.rs
    │   └── role_menu.rs
    ├── modules/
    │   ├── system/
    │   │   ├── user_handler.rs
    │   │   ├── user_service.rs
    │   │   ├── role_service.rs
    │   │   └── rbac_service.rs
    │   └── ticket/
    │       ├── ticket_handler.rs
    │       └── ticket_service.rs
    └── common/
        ├── error.rs
        ├── response.rs
        └── pagination.rs

我建议:

entity/      只放 SeaORM 自动生成的数据库实体
service/     写业务逻辑
handler/     写 HTTP 接口
migration/   管理数据库结构变更
infra/db.rs  管理数据库连接池

不要在 entity 里写业务逻辑。Entity 最好保持干净,方便以后重新生成。

三十三、SeaORM 入门记忆口诀

查数据:Entity::find()
查单条:Entity::find_by_id(id).one(db)
查多条:Entity::find().all(db)
加条件:filter(Column::Xxx.eq(value))
排序:order_by_desc(Column::Id)
分页:paginate(db, page_size)
新增:ActiveModel { field: Set(value), ..Default::default() }.insert(db)
修改:Model -> ActiveModel -> field = Set(value) -> update(db)
批量改:Entity::update_many()
事务:db.transaction(...)

三十四、你应该优先掌握的 8 个 API

Entity::find()
Entity::find_by_id(id)
query.filter(...)
query.order_by_desc(...)
query.paginate(...)
active_model.insert(db)
active_model.update(db)
db.transaction(...)

掌握这些,普通后台系统 70% 的数据库操作都能写了。