Diesel ORM
开篇故事
想象你要写 SQL 查询。传统方式是:手写 SQL → 执行 → 手动映射结果 → 处理类型错误。Diesel 就像智能助手——你定义数据结构,它生成类型安全的 SQL,编译时就告诉你有没有错误。
本章适合谁
如果你需要在 Rust 程序中使用 ORM(对象关系映射)操作数据库,本章适合你。Diesel 是类型安全的 ORM,在编译时捕获 SQL 错误。
你会学到什么
完成本章后,你可以:
- 定义 Diesel Schema
- 创建数据模型结构体
- 执行 CRUD 操作
- 使用类型安全的查询构建器
- 处理数据库连接和事务
前置要求
- 结构体 - 结构体定义
- Serde 序列化 - 序列化基础
- SQLx 数据库 - 数据库基础(可选)
依赖安装
运行以下命令安装所需依赖:
cargo add diesel --features postgres
cargo add dotenvy
第一个例子
最简单的 Diesel SQLite 示例:
use diesel::prelude::*;
use diesel::sqlite::SqliteConnection;
// 定义 Schema
diesel::table! {
posts (id) {
id -> Integer,
title -> Text,
body -> Text,
published -> Bool,
}
}
// 定义模型
#[derive(Queryable)]
struct Post {
id: i32,
title: String,
body: String,
published: bool,
}
fn main() {
// 连接数据库
let mut conn = SqliteConnection::establish("test.db")
.expect("Error connecting to database");
// 查询数据
let posts = posts::table
.load::<Post>(&mut conn)
.expect("Error loading posts");
for post in posts {
println!("{}: {}", post.title, post.body);
}
}
完整示例: diesel_sample.rs
原理解析
Diesel 特性
Diesel 是一个 ORM:
- ✅ 编译时 SQL 验证
- ✅ 类型安全查询
- ✅ 自动映射结果
- ✅ 支持多种数据库(SQLite, MySQL, PostgreSQL)
Schema 定义
使用 diesel::table! 宏:
diesel::table! {
users (id) {
id -> Integer,
name -> Text,
email -> Text,
age -> Integer,
}
}
字段类型映射:
Integer→ i32Text→ StringBool→ boolNullable<Text>→ Option
模型定义
使用 Queryable derive:
use diesel::Queryable;
#[derive(Queryable)]
struct User {
id: i32,
name: String,
email: String,
age: i32,
}
使用 Insertable derive:
use diesel::Insertable;
#[derive(Insertable)]
#[diesel(table_name = users)]
struct NewUser<'a> {
name: &'a str,
email: &'a str,
age: i32,
}
连接数据库
建立连接:
use diesel::sqlite::SqliteConnection;
use diesel::Connection;
let mut conn = SqliteConnection::establish("database.db")
.expect("Error connecting to database");
使用环境变量:
use dotenvy::dotenv;
use std::env;
dotenv().ok();
let database_url = env::var("DATABASE_URL")
.unwrap_or_else(|_| "test.db".into());
let mut conn = SqliteConnection::establish(&database_url)
.expect("Error connecting to database");
CRUD 操作
Create (插入):
use diesel::RunQueryDsl;
let new_post = NewPost {
title: "My Post",
body: "Post body",
published: true,
};
diesel::insert_into(posts::table)
.values(&new_post)
.execute(&mut conn)
.expect("Error saving new post");
Read (查询):
use diesel::RunQueryDsl;
// 查询所有
let all_posts = posts::table
.load::<Post>(&mut conn)
.expect("Error loading posts");
// 查询单个
let post = posts::table
.find(post_id)
.first::<Post>(&mut conn)
.expect("Error loading post");
// 条件查询
let published_posts = posts::table
.filter(posts::published.eq(true))
.load::<Post>(&mut conn)
.expect("Error loading posts");
Update (更新):
use diesel::RunQueryDsl;
diesel::update(posts::table.find(post_id))
.set(posts::published.eq(true))
.execute(&mut conn)
.expect("Error updating post");
Delete (删除):
diesel::delete(posts::table.find(post_id))
.execute(&mut conn)
.expect("Error deleting post");
查询构建器
过滤:
posts::table
.filter(posts::published.eq(true))
.filter(posts::title.like("%Rust%"))
.load::<Post>(&mut conn)?;
排序:
posts::table
.order(posts::created_at.desc())
.load::<Post>(&mut conn)?;
限制数量:
posts::table
.limit(10)
.offset(20)
.load::<Post>(&mut conn)?;
连接表:
posts::table
.inner_join(users::table)
.select((posts::all_columns, users::name))
.load::<(Post, String)>(&mut conn)?;
常见错误
错误 1: Schema 不匹配
// Schema 定义
diesel::table! {
posts (id) {
id -> Integer,
title -> Text,
}
}
// 模型
#[derive(Queryable)]
struct Post {
id: i32,
title: String,
body: String, // ❌ 数据库中不存在 body 字段
}
错误信息:
column `body` does not exist
修复方法:
#[derive(Queryable)]
struct Post {
id: i32,
title: String, // ✅ 只包含存在的字段
}
错误 2: 忘记 derive
// ❌ 忘记 #[derive(Queryable)]
struct Post {
id: i32,
title: String,
}
错误信息:
the trait `Queryable<_, __>` is not implemented for `Post`
修复方法:
#[derive(Queryable)] // ✅ 添加 derive
struct Post {
id: i32,
title: String,
}
错误 3: 连接未使用 mut
let conn = SqliteConnection::establish("test.db")?;
posts::table.load::<Post>(&conn)?; // ❌ 需要 &mut conn
修复方法:
let mut conn = SqliteConnection::establish("test.db")?;
posts::table.load::<Post>(&mut conn)?; // ✅ 使用 &mut
动手练习
练习 1: 定义用户 Schema
// TODO: 定义 users 表 Schema
// 字段:id (Integer), name (Text), email (Text)
// TODO: 定义 User 结构体
// 实现 Queryable trait
点击查看答案
diesel::table! {
users (id) {
id -> Integer,
name -> Text,
email -> Text,
}
}
#[derive(Queryable)]
struct User {
id: i32,
name: String,
email: String,
}
练习 2: 插入和查询用户
// TODO: 创建数据库连接
// TODO: 插入 3 个用户
// TODO: 查询所有用户
// TODO: 打印用户列表
点击查看答案
let mut conn = SqliteConnection::establish("test.db")?;
// 插入
let new_user = NewUser {
name: "Alice",
email: "alice@example.com",
};
diesel::insert_into(users::table)
.values(&new_user)
.execute(&mut conn)?;
// 查询
let users = users::table.load::<User>(&mut conn)?;
for user in users {
println!("{} - {}", user.name, user.email);
}
练习 3: 条件查询
// TODO: 查询已发布的博客
// TODO: 按创建时间排序
// TODO: 限制返回 10 条
点击查看答案
let posts = posts::table
.filter(posts::published.eq(true))
.order(posts::created_at.desc())
.limit(10)
.load::<Post>(&mut conn)?;
故障排查 (FAQ)
Q: Diesel 和 SQLx 有什么区别?
A:
- Diesel: ORM,编译时 SQL 验证,类型安全更强
- SQLx: 异步,运行时 SQL 验证,更灵活
- 推荐: Diesel(类型安全更好)
Q: 如何处理关联关系?
A:
#[derive(Associations, Queryable)]
#[diesel(belongs_to(User))]
struct Post {
id: i32,
user_id: i32,
title: String,
}
Q: 如何运行迁移?
A:
# 安装 diesel CLI
cargo install diesel_cli
# 创建迁移
diesel migration generate create_posts_table
# 运行迁移
diesel migration run
知识扩展
事务处理
use diesel::Connection;
conn.transaction::<_, diesel::result::Error, _>(|conn| {
// 所有操作在一个事务中
diesel::insert_into(posts::table)
.values(&new_post)
.execute(conn)?;
diesel::insert_into(comments::table)
.values(&new_comment)
.execute(conn)?;
Ok(())
})?;
关联查询
// 查询用户及其所有博客
let results = users::table
.inner_join(posts::table)
.select((User::as_select(), Post::as_select()))
.load::<(User, Post)>(&mut conn)?;
动态查询
let mut query = posts::table.into_boxed();
if let Some(title) = filter_title {
query = query.filter(posts::title.like(format!("%{}%", title)));
}
if let Some(user) = filter_user {
query = query.filter(posts::user_id.eq(user.id));
}
let posts = query.load::<Post>(&mut conn)?;
小结
核心要点:
- Diesel: 类型安全的 ORM
- Schema: 使用 table! 宏定义
- Queryable: 自动映射查询结果
- Insertable: 类型安全的插入
- 查询构建器: filter, order, limit 等
- 事务: 使用 transaction 方法
关键术语:
- ORM (对象关系映射): 数据库表映射到结构体
- Schema: 数据库结构定义
- Queryable: 查询结果映射 trait
- Insertable: 插入数据 trait
- Transaction: 数据库事务
术语表
| English | 中文 |
|---|---|
| ORM | 对象关系映射 |
| Schema | 模式/架构 |
| Queryable | 可查询 |
| Insertable | 可插入 |
| Transaction | 事务 |
| Migration | 迁移 |
知识检查
快速测验(答案在下方):
-
Diesel 的 schema 是什么?
-
Queryable和Insertable有什么区别? -
如何处理数据库迁移?
点击查看答案与解析
- 数据库表结构的 Rust 表示(table! 宏)
Queryable= 查询结果映射,Insertable= 插入数据映射- 使用 Diesel CLI:
diesel migration generate和diesel migration run
关键理解: Diesel 是类型安全的 ORM,编译时检查查询。
继续学习
前一章: Futures 异步编程
下一章: 宏编程
相关章节:
- SQLx 数据库
- Futures 异步编程
- 序列化
返回: 高级进阶
完整示例: diesel_sample.rs