目录
- 一、SeaORM 是什么?
- 二、SeaORM 核心概念
- 三、推荐学习路线
- 四、创建项目
- 五、准备 MySQL 数据库
- 六、配置 Cargo.toml
- 七、安装 sea-orm-cli
- 八、初始化 Migration
- 九、修改 migration/Cargo.toml
- 十、创建用户表 Migration
- 十一、执行 Migration
- 十二、生成 Entity
- 十三、main.rs 引入 entity
- 十四、封装数据库连接
- 十五、第一个查询:查询全部用户
- 十六、按 ID 查询
- 十七、条件查询
- 十八、新增用户
- 十九、批量新增
- 二十、修改用户
- 二十一、批量修改
- 二十二、删除用户
- 二十三、分页查询
- 二十四、事务
- 二十五、加上角色表和用户角色表
- 二十六、关联查询思路
- 二十七、一个完整 UserService 示例
- 二十八、main.rs 测试调用
- 二十九、SeaORM 常见 Trait 速查
- 三十、SeaORM 和 SQL 的对应关系
- 三十一、常见坑
- 三十二、推荐项目落地结构
- 三十三、SeaORM 入门记忆口诀
- 三十四、你应该优先掌握的 8 个 API
下面给你一份 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 里通常包含 up 和 down 两个方法,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% 的数据库操作都能写了。