vault backup: 2026-05-17 00:06:11

This commit is contained in:
hhs
2026-05-17 00:06:11 +08:00
parent 0e67673d7a
commit bdeb889ee4
57 changed files with 16744 additions and 39 deletions
+211 -12
View File
@@ -1,5 +1,5 @@
---
tags: [GORM, Go, ORM, API, 链式调用, Session, Clauses, Context]
tags: [GORM, Go, ORM, API, 链式调用, Session, Clauses, Context, Scopes]
create time: 2026-05-06 00:00
---
@@ -48,12 +48,40 @@ db.Table("user_view").Select("name, email").Find(&results)
db.Table("users").Where("deleted_at IS NULL").Count(&count)
```
> [!note] `Model()` 还能做级联更新
> 当需要更新某个关联记录的所属关系时,用 `Model()` 指定主模型、`Omit()` 排除无关字段:
> ```go
> // 将订单 user_id 改为 5(只改这一个字段,不触发钩子)
> db.Model(&Order{}).Where("id = ?", orderID).Omit("updated_at").Update("user_id", 5)
> ```
### `Model()` — 命名策略与前缀自动处理
当使用 `Model(&User{})` 时,GORM 会按配置中的 `NamingStrategy` 解析表名:
```go
// 假设配置了前缀 + 单数模式
db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{
NamingStrategy: schema.NamingStrategy{
TablePrefix: "crm_",
SingularTable: true,
},
})
db.Model(&User{}).Find(&users)
// → SELECT * FROM crm_user ✅ 自动加前缀 + 变单数
db.Table("users").Find(&users)
// → SELECT * FROM users ❌ 完全忽略命名策略
```
> [!warning] 常见陷阱:Table 绕过命名策略
> 团队协作中最容易犯的错误——有人为了"灵活"大量使用 `db.Table("xxx")`,后来改了 `TablePrefix`,结果查询查到了裸表(没有前缀)。排查这种问题非常耗时。
> **建议**:项目中统一使用 `Model()`,需要跨库时才退回到 `Table("other_db.table_name")`。
### `Model()` — 级联更新技巧
当需要更新某个关联记录的所属关系时,用 `Model()` 指定主模型、`Omit()` 排除无关字段:
```go
// 将订单 user_id 改为 5(只改这一个字段,不触发钩子)
db.Model(&Order{}).Where("id = ?", orderID).Omit("updated_at").Update("user_id", 5)
```
---
### `db.Session()` — 创建带配置的独立会话
@@ -161,6 +189,56 @@ db.Model(&Product{}).Where("id = ?", id).
> - **Updates 防零值污染**:结构体中有未传来的字段,默认零值会覆盖数据库数据,用 `Assign` 只写入你指定的值
> - **无主数据批量创建**:没有完整 Model,只有部分字段的临时数据
### `Scopes()` —— 可复用的查询条件链
Scopes 是 GORM 中**最容易被忽视但最有价值**的模式之一。它将一组查询条件封装成函数,像中间件一样在不同业务场景中复用:
```go
// 定义一个 Scope 函数——参数和返回值都是 *gorm.DB
func Active(db *gorm.DB) *gorm.DB {
return db.Where("status = ?", "active")
}
func CreatedAfter(t time.Time) *gorm.DB {
return db.Where("created_at > ?", t)
}
// 使用 —— 自由组合多个 Scope
var users []User
db.Scopes(Active, CreatedAfter(time.Now().AddDate(0, 0, -30))).
Order("created_at DESC").
Find(&users)
// WHERE status = 'active' AND created_at > '...' ORDER BY created_at DESC
```
> [!tip] Scopes vs 中间态 DB(变量保存 Where)
>
> | 维度 | Scopes(函数) | 中间态 DB(变量) |
> |------|---------------|------------------|
> | 复用粒度 | 跨模块、跨文件 | 同一请求内的不同分支 |
> | 可组合性 | `Scopes(f1, f2)` 自由组合 | 需要手动拼接 |
> | 参数化 | 支持闭包传入参数 | 直接在变量上追加 |
> | 测试友好 | 每个 Scope 独立可测 | 需构造完整 DB 对象 |
>
> ```go
> // 带参数的 Scope —— 通过闭包传参
> func Page(page, pageSize int) func(db *gorm.DB) *gorm.DB {
> return func(db *gorm.DB) *gorm.DB {
> offset := (page - 1) * pageSize
> return db.Offset(offset).Limit(pageSize)
> }
> }
>
> // 使用
> db.Scopes(Page(2, 20)).Find(&items)
> ```
> [!tip] Scope 编写规范
> - **参数始终是 `*gorm.DB`,返回值也是 `*gorm.DB`**——这是 GORM 约定的签名
> - 不要修改传入的 DB 的配置(如 Logger),只追加查询条件
> - Scope 可以组合任意数量的 Where / Order / Select / Omit 等操作
> - 需要 SQL 注入防护时(如动态排序字段),在 Scope 内部做白名单校验
### `WithContext()` / `Ctx` — 上下文集成
生产环境中经常需要超时控制、取消请求、传递 trace ID。GORM 完全兼容 Go 标准库的 `context.Context`。
@@ -256,6 +334,81 @@ if err := tx.Create(&user).Error; err != nil {
return tx.Commit().Error // 全部成功才提交
```
## API 速查参考卡
面向快速决策——面对一个具体需求时,不知道该用哪个 API?参考下面的对照表。
### 读取单条记录
| 方法 | SQL 行为 | 是否必须有条件 | 适用场景 |
|------|---------|--------------|---------|
| `First(&v, cond)` | `ORDER BY PK ASC LIMIT 1` + WHERE | ✅ 必须 | 按主键或条件取确定的一条 |
| `Take(&v)` | 无排序,随机一条 | ✅ 必须 | 抽奖、随机推荐 |
| `Last(&v, cond)` | `ORDER BY PK DESC LIMIT 1` | ✅ 必须 | 取最新的一条 |
> **经验法则**:拿不到记录时会返回 `gorm.ErrRecordNotFound`。**永远检查 `.Error`**。详见 [[03-CRUD 操作]]。
### 批量查询
| 方法 | 返回类型 | 是否需要 Where | 说明 |
|------|---------|--------------|------|
| `Find(&slice)` | `[]Model` | ❌ 可选 | 默认查所有匹配行,**不加 Where = 全表扫描** |
| `Pluck("col", &slice)` | 切片(非 Model) | ❌ 可选 | 只取一列的值,如 `[]string{"Alice","Bob"}` |
| `Scan(&dest)` | 任意结构体 | ❌ 可选 | 把结果映射到自定义结构体 |
### 更新策略对比
| 方法 | 参数类型 | 零值处理 | 触发 Hooks | 典型场景 |
|------|---------|---------|-----------|---------|
| `Update(col, val)` | string + any | — | ✅ | 改单个字段 |
| `Updates(map)` | `map[string]any` | **写入零值** | ✅ | 前端完整表单提交 |
| `Updates(struct)` | struct | **跳过零值** | ✅ | 部分字段修改 |
| `Save` | struct | **全部写入** | ✅ | 全量覆盖(谨慎使用) |
| `UpdateColumn(...)` | 同 Update | — | ❌ 跳过 | 绕过钩子直接写 |
> [!warning] Updates 的零值陷阱
> ```go
> // 用户年龄改为 0 —— 如果用 struct,Age=0 被跳过,数据库不变!
> db.Model(&user).Updates(User{Name: "Alice", Age: 0}) // ⚠️ Age 没变
>
> // ✅ 改用 map
> db.Model(&user).Updates(map[string]any{"name": "Alice", "age": 0}) // ✅ Age 变为 0
> ```
> 详见 [[03-CRUD 操作]]。
### 创建方式对比
| 方法 | 适用场景 | 批次控制 | 说明 |
|------|---------|---------|------|
| `Create(&model)` | 单条插入 | 无 | 最基础的插入 |
| `Create(&slice)` | 中小批量(≤10K) | GORM 自动拆批 ~256 | 内部拆分为多条 INSERT |
| `CreateInBatches(&slice, n)` | 超大批量(>10K) | 可指定 batch size | 适合数据导入、迁移 |
| `Clauses(OnConflict)...Create()` | Upsert | 同上 | 存在则更新,不存在则插入 |
> 详见 [[11-批量操作]]。
### 字段选择
| 方法 | 行为 | 示例 |
|------|------|------|
| `Select("col1, col2")` | 白名单:只读这些列 | `db.Select("id,name").Find(&users)` |
| `Omit("col1", "col2")` | 黑名单:不读这些列 | `db.Omit("password","bio").Find(&users)` |
| `Pluck("col", &dest)` | 只取一列的值 | `db.Pluck("email", &emails)` |
> **选择策略**:需要的列少 → Omit;需要的列多 → Select。敏感字段永远建议 Omit。
### 删除方式
| 方法 | 行为 | 是否可恢复 |
|------|------|---------|
| `Delete(&model)` | 软删除(有 DeletedAt 时)→ UPDATE | ✅ 可恢复 |
| `Where(...).Delete(&Model{})` | 条件批量删除 | 取决于模型是否有软删除 |
| `Unscoped().Delete(...)` | 物理删除(忽略软删除) | ❌ 不可恢复 |
> 详见 [[10-软删除]]。
---
## 常见链式组合 Recipes
按场景给出最常用的一行写法模板:
@@ -330,17 +483,56 @@ flowchart TD
Control -->|"跳过钩子/调试"| Session["&gorm.Session{SkipHooks/DryRun}"]
Control -->|"关联赋值"| Assign["Assign(值).Create/Update"]
Control -->|"超时/取消"| Context["WithContext(ctx)"]
Control -->|"条件需复用"| Scopes["Scopes(f1, f2)"]
Control -->|"普通查询继续链式调用"| Chain["正常链式调用 Where/Order/Limit"]
Chain --> Result["Find/First/Create/Update/Delete"]
Clauses --> Result
Chain --> MethodQ{"读取几条?"}
MethodQ --> |1条| SingleQ{需要确定性?}
SingleQ --> |是| First["First - ORDER BY PK ASC LIMIT 1"]
SingleQ --> |否_随机| Take["Take - 随机一条"]
MethodQ --> |N条| FindMode{"查哪些列?"}
FindMode --> |全列| Find["Find - 加 Where 过滤"]
FindMode --> |单列| Pluck["Pluck - 只取一列"]
FindMode --> |部分列| SelectOmit{"少选多?"}
SelectOmit --> |选少| Omit["Omit - 排除法"]
SelectOmit --> |选多| Select["Select - 白名单"]
MethodQ --> |超大结果集| Rows["Rows() 游标逐行消费"]
Chain --> UpdateQ{"改几个字段?"}
UpdateQ --> |1个| OneField["Update(col, val)"]
UpdateQ --> |部分| ZeroQ{含零值?}
ZeroQ --> |是_map_| MapUpdate["Updates(map)"]
ZeroQ --> |否_struct_| StructUpdate["Updates(struct)"]
UpdateQ --> |全部| FullUpdate["Save - 全量覆盖"]
UpdateQ --> |无钩子需求| NoHook["UpdateColumn / UpdateColumns"]
Clauses --> Result["执行 CRUD 动作"]
Session --> Result
Assign --> Result
Context --> Result
Scopes --> Result
First --> Result
Take --> Result
Find --> Result
Pluck --> Result
Omit --> Result
Select --> Result
Rows --> Result
OneField --> Result
MapUpdate --> Result
StructUpdate --> Result
FullUpdate --> Result
NoHook --> Result
style Start fill:#4FC08D,color:#fff
style Model fill:#3B82F6,color:#fff
style Table fill:#EAB308,color:#000
style Scopes fill:#8B5CF6,color:#fff
style Result fill:#10B981,color:#fff
```
@@ -348,6 +540,13 @@ flowchart TD
- [[01-安装与初始化]]
- [[02-模型定义]]
- [[03-CRUD 操作]]
- [[04-条件查询]]
- [[08-事务管理]]
- [[03-CRUD 操作]] — 详细版 First/Find/Create/Update/Delete
- [[04-条件查询]] — Where/Or/Between/In/Like 详解
- [[05-关联查询]] — Preload/Joins/Association
- [[06-排序与分页]] — Order/Limit/Paginate
- [[08-事务管理]] — Begin/Commit/Rollback
- [[09-钩子函数]] — LifecycleHooks/全局Callback
- [[10-软删除]] — SoftDelete/Unscoped
- [[11-批量操作]] — Bulk Insert/Update/Upsert
- [[14-错误处理]] — RecordNotFound/ErrDuplicatedKey
- [[15-性能优化]] — N+1/Preload策略/预编译语句
+7 -6
View File
@@ -241,9 +241,10 @@ db.Model(&Article{}).Omit("content", "password").Find(&articles)
```
> [!tip] `Omit` vs `Select` 的选择策略
> - 需要的列少(比如 3 个字段中有 20 个)→ `Omit` 更省事
> - 需要的列多 → `Select` 更清晰
> - 敏感字段(密码、内部密钥)永远建议 `Omit`,以防误暴露
> - **要查询的列很少**(例如 3/20)→ `Select` 更简洁,明确列出所需字段
> - **要排除的列很少**(例如 3 个不要 / 17 个保留)→ `Omit` 更省事
> - **原则**:谁的参数少就用谁,代码更直观
> - **敏感字段**(密码、密钥等)永远用 `Omit` 排除——白名单式防护,防止重构或新增字段时意外暴露
### Distinct — 去重查询
@@ -319,10 +320,10 @@ fmt.Println(user.ID) // 插入后 ID 自动回填到结构体
| 方法 | 签名 | 行为 |
|------|------|------|
| `Update` | `(col string, value any)` | 更新单个字段 |
| `Updates` | `(value Model | map[string]any)` | 更新一个或多个字段 |
| `Updates` | `(value Model / map[string]any)` | 更新一个或多个字段 |
| `Save` | `(value Model)` | 全量更新所有字段 |
| `UpdateColumn` | `(col string, value any)` | 同 Update 但不触发 Hooks |
| `UpdateColumns` | `(value Model)` | 同 Updates 但不触发 Hooks |
| `UpdateColumn` | `(col string, value any)` | 同 `Update` 但不触发 Hooks |
| `UpdateColumns` | `(value Model)` | 同 `Updates` 但不触发 Hooks |
### 详细示例
+35 -21
View File
@@ -246,30 +246,44 @@ q.Order("created_at DESC").Limit(10).Find(&activeUsers)
```mermaid
flowchart TD
Start["收到查询请求"] --> Type{"条件类型"}
Type --> "精确匹配" --> Exact["Where field, value"]
Type --> "范围查询" --> RangeQ{"BETWEEN 还是 GT/LT"}
RangeQ --> "GORM 自带" --> Between["BETWEEN ? AND ?"]
RangeQ --> "自定义 < >" --> LtGt["field > ? AND field < ?"]
Type --> "集合" --> SetQ{"IN 还是 NOT IN"}
SetQ --> "IN" --> InOp["In field VALUES"]
SetQ --> "NOT IN" --> NotInOp["Not In field VALUES"]
Type --> "模糊" --> LikeQ{"前缀/后缀/全匹配"}
LikeQ --> "前缀 %" --> PrefixLike["LIKE 'prefix%'"]
LikeQ --> "后缀 %" --> SuffixLike["LIKE '%suffix'"]
LikeQ --> "全匹配 %" --> FullLike["LIKE '%keyword%'"]
Type --> "NULL检查" --> NullQ{"IS NULL 还是 NOT"}
NullQ --> "IS NULL" --> IsNullOp["IS NULL"]
NullQ --> "IS NOT NULL" --> IsNotNullOp["IS NOT NULL"]
Type --> "复合条件" --> AndQ{"简单AND 还是 OR混合"}
AndQ --> "简单AND" --> SimpleAnd["连续 Where 调用"]
AndQ --> "OR混合" --> OrChain["Where + Or 链式"]
Type --> "特殊SQL" --> Raw["Raw + Expr"]
Type --> Exact["精确匹配\nWhere field, value"]
Type --> RangeQ{"范围查询\nBETWEEN 还是 GT/LT"}
RangeQ --> Between["GORM 自带\nBETWEEN ? AND ?"]
RangeQ --> LtGt["自定义 < >\nfield > ? AND field < ?"]
Type --> SetQ{"集合\nIN 还是 NOT IN"}
SetQ --> InOp["IN\nIn field VALUES"]
SetQ --> NotInOp["NOT IN\nNot In field VALUES"]
Type --> LikeQ{"模糊匹配\n前缀 / 后缀 / 全匹配"}
LikeQ --> PrefixLike["LIKE 'prefix%'"]
LikeQ --> SuffixLike["LIKE '%suffix'"]
LikeQ --> FullLike["LIKE '%keyword%'"]
Type --> NullQ{"NULL 检查\nIS NULL 还是 NOT"}
NullQ --> IsNullOp["IS NULL"]
NullQ --> IsNotNullOp["IS NOT NULL"]
Type --> AndQ{"复合条件\n简单 AND 还是 OR 混合"}
AndQ --> SimpleAnd["简单 AND\n连续 Where 调用"]
AndQ --> OrChain["OR 混合\nWhere + Or 链式"]
Type --> Raw["特殊 SQL\nRaw + Expr"]
style Start fill:#4FC08D,color:#fff
style Exact fill:#3B82F6,color:#fff
style LikeOp fill:#F59E0B,color:#000
style Between fill:#3B82F6,color:#fff
style LtGt fill:#3B82F6,color:#fff
style InOp fill:#3B82F6,color:#fff
style NotInOp fill:#3B82F6,color:#fff
style LikeQ fill:#F59E0B,color:#000
style PrefixLike fill:#F59E0B,color:#000
style SuffixLike fill:#F59E0B,color:#000
style FullLike fill:#F59E0B,color:#000
style NullQ fill:#8B5CF6,color:#fff
style IsNullOp fill:#8B5CF6,color:#fff
style IsNotNullOp fill:#8B5CF6,color:#fff
style AndQ fill:#EC4899,color:#fff
style SimpleAnd fill:#EC4899,color:#fff
style OrChain fill:#EC4899,color:#fff
style Raw fill:#EC4899,color:#fff
style Start fill:#4FC08D,color:#fff
```
## 常见坑点速查