1- # 数据库模块
1+ # 数据库
22
3- 支持 MySQL / PostgreSQL / SQLite 三种数据库的 ORM 和查询功能,通过 Cargo features 开启 。
3+ 支持 MySQL / PostgreSQL / SQLite 数据库的 ORM 和查询功能,基于 [ sqlx ] ( https://github.com/launchbadge/sqlx ) 实现 。
44
55## 启用
66
@@ -15,80 +15,103 @@ webr = { version = "0.1", features = ["mysql"] } # 或 "postgres", "sqlite"
1515
1616``` toml
1717[datasource ]
18- driver = " sqlite "
19- database = " todos.db"
18+ # SQLite
19+ url = " sqlite:// todos.db?mode=rwc "
2020
21- # 或 PostgreSQL:
22- # driver = "postgres"
23- # url = "postgres://user:pass@localhost:5432/mydb"
24- # host = "localhost"
25- # port = 5432
26- # username = "user"
27- # password = "pass"
21+ # MySQL
22+ # url = "mysql://localhost:3306/db"
23+ # user = "user"
24+ # password = "password"
2825
26+ # PostgreSQL
27+ # url = "postgres://localhost:5432/db"
28+ # user = "user"
29+ # password = "password"
30+
31+
32+ # 连接池配置(可选)
2933[datasource .pool ]
3034max_connections = 10
3135min_connections = 0
3236connect_timeout_secs = 30
3337idle_timeout_secs = 600
3438```
3539
36- 如果配置了 ` url ` ,则直接使用完整连接字符串;否则根据 ` driver ` + 各字段拼接。
37-
3840## 初始化连接池
3941
42+ ### 手动初始化
43+
4044``` rust
4145use webr :: db :: {DbPool , DatasourceConfig };
4246
4347#[webr:: main]
4448async fn main (app : & mut AppBuilder ) -> Result <(), Error > {
49+ // 获取数据源配置
4550 let ds_config = app . config ()
4651 . get :: <DatasourceConfig >(" datasource" )
4752 . map_err (| e | Error :: Internal (e . to_string ()))? ;
4853
54+ // 创建连接池
4955 let pool = DbPool :: from_config (& ds_config ). await
5056 . map_err (| e | Error :: Database (Box :: new (e )))? ;
5157
52- webr :: db :: set_pool (pool . inner (). clone ()); // 设置全局池
53- app . provide (pool )? ; // 注册到 DI 容器
58+ // 设置全局池
59+ webr :: db :: set_pool (pool . inner (). clone ());
60+
61+ // 注册到容器
62+ app . provide (pool )? ;
63+
5464 Ok (())
5565}
5666```
5767
58- 或者启用 ` auto-init ` feature 自动初始化:
68+ ### 自动初始化
69+
70+ 启用 ` auto-init ` feature 自动初始化连接池。
5971
6072``` toml
6173webr = { features = [" sqlite" , " auto-init" ] }
6274```
6375
64- ` auto-init ` 自动检测 ` [datasource] ` 配置节,自动创建连接池并注册到 DI 容器。
76+ ` auto-init ` 自动检测 ` [datasource] ` 配置节,自动创建连接池并注册到容器。
77+
78+ ## ` #[entity] ` 宏
6579
66- ## # [ entity ] 实体定义
80+ 标记一个 ` struct ` 为数据库实体,自动生成CRUD等关联函数。
6781
6882``` rust
6983#[webr:: entity(table = " todos" )]
7084#[derive(Debug , Clone , Serialize , Deserialize )]
7185pub struct Todo {
72- #[column(pk)] // 标记主键
86+ // 标记主键
87+ #[column(pk)]
7388 pub id : i64 ,
7489 pub title : String ,
7590 pub done : bool ,
7691}
7792```
7893
79- ` #[entity] ` 宏自动生成:
80- - ` Iden ` 枚举(用于 sea-query 构建查询)
81- - CRUD 方法:` find_all() ` , ` find_by_id() ` , ` save() ` , ` delete() `
82- - 字段属性:` #[column(pk)] ` 主键、` #[column(name = "col")] ` 自定义列名
94+ ` #[entity] ` 宏自动生成以下函数:
95+
96+ | 函数 | 返回值 | 说明 |
97+ | --------------------------------| ------------------------| ---------------------|
98+ | ` find_by_id(id: &PkType) ` | ` Result<Option<Self>> ` | 按主键查询单条记录 |
99+ | ` find_all() ` | ` Result<Vec<Self>> ` | 查询全部记录 |
100+ | ` find_page(pager: Pagination) ` | ` Result<Page<Self>> ` | 分页查询 |
101+ | ` save(&self) ` | ` Result<()> ` | 插入实体,忽略 ` None ` 字段 |
102+ | ` save_batch(items: &[Self]) ` | ` Result<u64> ` | 批量插入,生成单条 INSERT 语句 |
103+ | ` update(&self) ` | ` Result<bool> ` | 按主键更新,只更新 ` Some ` 字段 |
104+ | ` delete(&self) ` | ` Result<bool> ` | 按主键删除 |
105+ | ` count() ` | ` Result<i64> ` | 统计总记录数 |
83106
84107### CRUD 示例
85108
86109``` rust
87- // 查询全部(自动使用全局池)
110+ // 查询全部
88111let todos = Todo :: find_all (). await ? ;
89112
90113// 按 ID 查询
91- let todo = Todo :: find_by_id (& 42 ). await ? ;
114+ let todo = Todo :: find_by_id (42 ). await ? ;
92115
93116// 保存(INSERT + 返回完整记录)
94117let saved = todo . save (). await ? ;
@@ -97,7 +120,7 @@ let saved = todo.save().await?;
97120let deleted = todo . delete (). await ? ;
98121```
99122
100- ## #[ sql] 动态查询
123+ ## #[ sql] 宏
101124
102125支持 MyBatis 风格的动态 SQL 标签。
103126
@@ -133,7 +156,7 @@ pub async fn search(
133156}
134157```
135158
136- ** ` <where> ` ** — 自动处理 WHERE 关键字,去除多余的 AND/OR :
159+ ** ` <where> ` ** — 条件查询 :
137160
138161``` rust
139162// 当 title = None, done = Some(true) 时生成:
@@ -175,7 +198,26 @@ pub async fn search_sorted(
175198}
176199```
177200
178- ** ` <trim> ` ** — 自定义前缀后缀修整。
201+ ** ` <trim> ` ** — 自定义前缀/后缀,并自动去除多余关键字:
202+
203+ ``` rust
204+ #[sql(r # "
205+ UPDATE todos
206+ <trim prefix="SET" suffixOverrides=",">
207+ <if test="title">title = #{title},</if>
208+ <if test="done">done = #{done},</if>
209+ </trim>
210+ WHERE id = #{id}
211+ " # )]
212+ pub async fn update_optional (
213+ pool : & webr :: db :: DbPool ,
214+ id : i64 ,
215+ title : Option <& str >,
216+ done : Option <bool >,
217+ ) -> Result <()> {
218+ unreachable! ()
219+ }
220+ ```
179221
180222### 自定义返回类型
181223
@@ -203,7 +245,7 @@ pub async fn list_tuples(pool: &webr::db::DbPool) -> Result<Vec<(i64, String)>>
203245
204246### 分页查询
205247
206- 使用 ` Pagination ` 参数进行分页:
248+ 使用 ` Pagination ` 参数进行分页。
207249
208250``` rust
209251use webr :: db :: Pagination ;
@@ -225,25 +267,24 @@ pub async fn search_page(
225267
226268// 使用
227269let pager = Pagination :: new (1 , 20 );
228- let page = Todo :: search_page (& pool , Some (" rust" ), pager ). await ? ;
229- // page.items, page.total, page.page, page.page_size, page.total_pages, page.has_next, page.has_prev
270+ let page = Todo :: search_page ( & pool , Some (" rust" ), pager ). await ? ;
230271```
231272
232273` Page<T> ` 字段:
233274
234- | 字段 | 类型 | 说明 |
235- | ------| ------| ------|
236- | items | Vec\< T\> | 当前页数据 |
237- | total | i64 | 总记录数 |
238- | page | u64 | 当前页码 |
239- | page_size | u64 | 每页条数 |
240- | total_pages | u64 | 总页数 |
241- | has_next | bool | 是否有下一页 |
242- | has_prev | bool | 是否有上一页 |
275+ | 字段 | 类型 | 说明 |
276+ | ------------- | ---------- | -- ------|
277+ | items | Vec\< T\> | 当前页数据 |
278+ | total | i64 | 总记录数 |
279+ | page | u64 | 当前页码 |
280+ | page_size | u64 | 每页条数 |
281+ | total_pages | u64 | 总页数 |
282+ | has_next | bool | 是否有下一页 |
283+ | has_prev | bool | 是否有上一页 |
243284
244- ## # [ tx ] 事务管理
285+ ## 事务
245286
246- ### 声明式事务
287+ ### ` #[tx] ` 声明式事务
247288
248289在 impl block 上标注 ` #[tx] ` ,其下所有 ` async fn ` 自动包装在事务中:
249290
@@ -282,10 +323,10 @@ impl TodoService {
282323``` rust
283324use webr :: db :: {DbTransaction , scope_txn, try_get_txn};
284325
285- let txn = DbTransaction :: begin (& pool ). await ? ;
286- let result = scope_txn (& txn , async {
287- // 事务中的操作...
288- Ok :: <_ , Error >(())
326+ let txn = DbTransaction :: begin ( & pool ). await ? ;
327+ let result = scope_txn ( & txn , async {
328+ // 事务中的操作...
329+ Ok :: <_ , Error >(())
289330}). await ;
290331txn . commit (). await ? ; // 或 txn.rollback().await?;
291332```
@@ -294,17 +335,17 @@ txn.commit().await?; // 或 txn.rollback().await?;
294335
295336``` rust
296337// fetch_all: 查询多行
297- pool . fetch_all :: <Todo >(" SELECT * FROM todos WHERE done = ?" , | b | b . bind (false )). await ? ;
338+ pool . fetch_all :: <Todo >(" SELECT * FROM todos WHERE done = ?" , | b | b . bind (false )). await ? ;
298339
299340// fetch_optional: 查询可选单行
300- pool . fetch_optional :: <Todo >(" SELECT * FROM todos WHERE id = ?" , | b | b . bind (42 )). await ? ;
341+ pool . fetch_optional :: <Todo >(" SELECT * FROM todos WHERE id = ?" , | b | b . bind (42 )). await ? ;
301342
302343// fetch_one: 查询确切一行(无数据则报错)
303- pool . fetch_one :: <Todo >(" SELECT * FROM todos WHERE id = ?" , | b | b . bind (42 )). await ? ;
344+ pool . fetch_one :: <Todo >(" SELECT * FROM todos WHERE id = ?" , | b | b . bind (42 )). await ? ;
304345
305346// execute: INSERT/UPDATE/DELETE,返回影响行数
306- pool . execute (" UPDATE todos SET done = ? WHERE id = ?" , | b | b . bind (true ). bind (42 )). await ? ;
347+ pool . execute (" UPDATE todos SET done = ? WHERE id = ?" , | b | b . bind (true ). bind (42 )). await ? ;
307348
308349// fetch_scalar: 标量查询
309- let count : i64 = pool . fetch_scalar (" SELECT COUNT(*) FROM todos" , | b | b ). await ? ;
350+ let count : i64 = pool . fetch_scalar (" SELECT COUNT(*) FROM todos" , | b | b ). await ? ;
310351```
0 commit comments