回调钩子模块原理说明

基于1.31 本文档深入解析回调钩子模块的设计原理和核心原理,涵盖事件驱动机制、责任链模式、钩子注册与执行等核心内容。


一、设计原理

1.1 模块定位

在整体架构中的位置

回调钩子模块是 GORM 的事件驱动层,位于查询构建和 SQL 执行之间,负责在数据操作的生命周期中插入自定义逻辑。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
┌─────────────────────────────────────────────────────────────┐
│ Finisher API │
│ db.Find(&users) │
└────────────────────────┬────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────┐
│ 回调系统 (本模块) ★ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ processor.Execute() │ │
│ │ ┌──────────────────────────────────────────────┐ │ │
│ │ │ 1. 执行 Scopes │ │ │
│ │ │ 2. 配置 BuildClauses │ │ │
│ │ │ 3. 应用 StatementModifier │ │ │
│ │ │ 4. 设置超时 │ │ │
│ │ │ 5. 解析模型 │ │ │
│ │ │ 6. 执行回调链 │ │ │
│ │ │ 7. 记录日志 │ │ │
│ │ └──────────────────────────────────────────────┘ │ │
│ └────────────────────────────────────────────────────┘ │
│ │
│ 回调链 (责任链模式): │
│ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ │
│ │ Before │ → │ Before │ → │ Core │ → │ After │ │
│ │ Query │ │ Query │ │ Query │ │ Query │ │
│ └────────┘ └────────┘ └────────┘ └────────┘ │
│ ↓ ↓ ↓ ↓ │
│ 用户钩子 GORM钩子 SQL构建 用户钩子 │
└────────────────────────┬────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────┐
│ SQL 构建与执行 │
│ 生成 SQL → 执行 → 扫描结果 │
└─────────────────────────────────────────────────────────────┘

核心价值

回调钩子模块的核心价值在于:

  1. 扩展性: 允许在不修改核心代码的情况下插入自定义逻辑
  2. 可测试性: 可以通过模拟钩子来测试边界情况
  3. 可维护性: 将横切关注点(如日志、验证)与业务逻辑分离
  4. 灵活性: 支持 Before/After/Replace/Remove 等多种操作

1.2 设计目的

问题 1: 如何在数据操作的各个阶段插入自定义逻辑,而不修改核心代码?

  • 挑战: 直接修改 GORM 核心代码会导致升级困难
  • 挑战: 不同项目有不同的需求(验证、日志、审计等)
  • 挑战: 需要保证执行顺序的正确性

解决方案: 事件驱动模式 + 责任链模式

1
2
3
4
5
6
7
8
9
10
11
传统方式:
修改 GORM 源码
└─ 在查询函数中添加验证代码
└─ 在创建函数中添加日志代码
└─ 升级 GORM 时需要重新合并代码

回调方式:
注册回调
├─ db.Callback().Create().Before("gorm:create").Register("validate", validateFunc)
├─ db.Callback().Query().After("gorm:query").Register("log", logFunc)
└─ 升级 GORM 时无需修改

问题 2: 如何保证回调的执行顺序?

  • 挑战: 某些回调必须在其他回调之前/之后执行
  • 挑战: 回调之间可能存在依赖关系
  • 挑战: 需要支持动态调整顺序

解决方案: Before/After 机制 + 拓扑排序

1
2
3
4
5
6
7
8
9
10
回调排序示例:

注册顺序:
1. Register("A", fnA)
2. Register("B", fnB).After("A")
3. Register("C", fnC).Before("A")

执行顺序: C → A → B

排序算法使用拓扑排序确保依赖关系正确

问题 3: 如何支持不同场景的回调执行?

  • 挑战: 某些回调只在特定条件下执行
  • 挑战: 需要支持条件性注册
  • 挑战: 需要支持替换和删除

解决方案: Match 条件 + Replace/Remove 机制

1
2
3
4
5
6
7
8
9
10
// 条件回调
db.Callback().Create().Match(func(db *gorm.DB) bool {
return !db.SkipDefaultTransaction
}).Register("gorm:begin_transaction", beginTx)

// 替换回调
db.Callback().Query().Replace("gorm:query", customQuery)

// 删除回调
db.Callback().Create().Remove("gorm:before_create")

1.3 结构安排依据

3 天学习时间的科学分配

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Day 1: 回调系统基础 (核心结构)
目标: 理解回调系统的整体架构
重点:
- callbacks 和 processor 结构
- 回调注册机制
- 默认回调列表

Day 2: 回调执行流程 (核心机制)
目标: 理解回调如何被执行
重点:
- processor.Execute() 流程
- 回调排序算法
- 回调链执行

Day 3: 高级特性 (扩展应用)
目标: 掌握高级回调技巧
重点:
- 模型钩子系统
- 条件回调
- 自定义回调最佳实践

由浅入深的学习路径

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
第 1 层: 理解层 (知道是什么)
├─ 回调是什么
├─ 回调什么时候执行
└─ 回调能做什么

第 2 层: 机制层 (知道如何工作)
├─ 回调如何注册
├─ 回调如何排序
└─ 回调如何执行

第 3 层: 应用层 (知道如何使用)
├─ 如何注册全局回调
├─ 如何实现模型钩子
└─ 如何实现条件回调

第 4 层: 原理层 (知道为什么这样设计)
├─ 事件驱动模式
├─ 责任链模式
└─ 拓扑排序算法

1.4 与其他模块的逻辑关系

依赖关系

  • 被查询构建调用: Finisher API 触发 processor.Execute()
  • 依赖 Schema: 模型钩子需要 Schema 信息
  • 依赖 Statement: 回调通过 Statement 访问查询上下文

支撑关系

  • 支撑子句系统: 回调可以修改 Statement.Clauses
  • 支撑事务系统: 事务回调通过 BeginTransaction/CommitOrRollback 实现
  • 支撑关联查询: Preload 回调实现预加载功能

与其他系统的交互

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
回调系统 ↔ 其他模块:

┌─────────────┐
│ 查询构建 │ → 触发回调执行
└──────┬──────┘


┌─────────────────────────────┐
│ 回调系统 │
│ ┌───────────────────────┐ │
│ │ 用户回调 │ │
│ │ GORM 内部回调 │ │
│ │ 模型钩子 │ │
│ └───────────────────────┘ │
└──────────┬──────────────────┘

├─→ 修改子句 (Clause 系统)
├─→ 管理事务 (事务系统)
├─→ 解析模型 (Schema 系统)
└─→ 执行 SQL (数据库驱动)

二、核心原理

2.1 关键概念

概念 1: callbacks 结构

定义: callbacks 是回调系统的容器,管理所有处理器。

1
2
3
4
5
6
7
8
9
10
type callbacks struct {
processors map[string]*processor
}

type processor struct {
db *DB
Clauses []string // 构建顺序
fns []func(*DB) // 编译后的函数列表
callbacks []*callback // 回调列表
}

设计原理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
callbacks 结构:

callbacks (容器)

├─► processors["create"] (创建处理器)
│ ├─ Clauses: ["INSERT", "VALUES", "ON CONFLICT"]
│ ├─ callbacks: [begin_transaction, before_create, ...]
│ └─ fns: [编译后的函数]

├─► processors["query"] (查询处理器)
│ ├─ Clauses: ["SELECT", "FROM", "WHERE", ...]
│ ├─ callbacks: [query, preload, after_query]
│ └─ fns: [编译后的函数]

├─► processors["update"] (更新处理器)
│ ├─ Clauses: ["UPDATE", "SET", "WHERE"]
│ ├─ callbacks: [begin_transaction, before_update, ...]
│ └─ fns: [编译后的函数]

├─► processors["delete"] (删除处理器)
│ ├─ Clauses: ["DELETE", "FROM", "WHERE"]
│ ├─ callbacks: [begin_transaction, before_delete, ...]
│ └─ fns: [编译后的函数]

├─► processors["row"] (行查询处理器)
│ └─ callbacks: [row]

└─► processors["raw"] (原始 SQL 处理器)
└─ callbacks: [raw]

默认回调列表:

操作 回调名称 位置 说明
Create gorm:begin_transaction 首位 开始事务
Create gorm:before_create 中间 创建前钩子
Create gorm:save_before_associations 中间 保存前置关联
Create gorm:create 核心 执行插入
Create gorm:save_after_associations 中间 保存后置关联
Create gorm:after_create 中间 创建后钩子
Create gorm:commit_or_rollback_transaction 末位 提交/回滚事务
Query gorm:query 核心 执行查询
Query gorm:preload 中间 预加载关联
Query gorm:after_query 末尾 查询后钩子
Update gorm:begin_transaction 首位 开始事务
Update gorm:before_update 中间 更新前钩子
Update gorm:save_before_associations 中间 保存前置关联
Update gorm:update 核心 执行更新
Update gorm:save_after_associations 中间 保存后置关联
Update gorm:after_update 中间 更新后钩子
Update gorm:commit_or_rollback_transaction 末位 提交/回滚事务
Delete gorm:begin_transaction 首位 开始事务
Delete gorm:before_delete 中间 删除前钩子
Delete gorm:delete_before_associations 中间 删除前置关联
Delete gorm:delete 核心 执行删除
Delete gorm:after_delete 中间 删除后钩子
Delete gorm:commit_or_rollback_transaction 末位 提交/回滚事务

概念 2: callback 结构

定义: callback 是单个回调的元数据和处理器。

1
2
3
4
5
6
7
8
9
10
type callback struct {
name string // 回调名称
before string // 在此回调之前执行
after string // 在此回调之后执行
remove bool // 是否删除
replace bool // 是否替换
match func(*DB) bool // 匹配条件
handler func(*DB) // 处理函数
processor *processor // 所属处理器
}

字段详解:

字段 类型 说明 示例
name string 回调唯一标识 “my_validate”
before string 在指定回调之前执行 Before(“gorm:create”)
after string 在指定回调之后执行 After(“gorm:query”)
remove bool 标记删除 Remove(“gorm:query”)
replace bool 标记替换 Replace(“gorm:query”, fn)
match func(*DB) bool 条件匹配函数 Match(func(db *DB) bool { return !db.SkipDefaultTransaction })
handler func(*DB) 实际执行的函数 func(db *DB) { … }

回调注册流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
注册回调:

db.Callback().Create().Before("gorm:create").Register("validate", fn)

├─► processor.Before("gorm:create")
│ └─► 返回 callback{before: "gorm:create"}

├─► callback.Register("validate", fn)
│ ├─► c.name = "validate"
│ ├─► c.handler = fn
│ ├─► processor.callbacks = append(..., c)
│ └─► processor.compile() // 编译回调

└─► compile 过程:
├─► 过滤: match 条件检查
├─► 删除: 移除标记为 remove 的回调
├─► 替换: 替换同名回调
├─► 排序: 根据 before/after 排序
└─► 编译: 生成 fns 列表

概念 3: processor.Execute() 执行流程

定义: Execute 是回调系统的核心执行方法。

完整执行流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
func (p *processor) Execute(db *DB) *DB {
// === 第 1 阶段: Scopes 执行 ===
for len(db.Statement.scopes) > 0 {
db = db.executeScopes()
}

// === 第 2 阶段: 初始化 ===
var (
curTime = time.Now()
stmt = db.Statement
resetBuildClauses bool
)

// === 第 3 阶段: 配置 BuildClauses ===
if len(stmt.BuildClauses) == 0 {
stmt.BuildClauses = p.Clauses
resetBuildClauses = true
}

// === 第 4 阶段: StatementModifier ===
if optimizer, ok := stmt.Dest.(StatementModifier); ok {
optimizer.ModifyStatement(stmt)
}

// === 第 5 阶段: 超时设置 ===
if db.DefaultContextTimeout > 0 {
if _, ok := stmt.Context.Deadline(); !ok {
stmt.Context, _ = context.WithTimeout(stmt.Context, db.DefaultContextTimeout)
}
}

// === 第 6 阶段: 模型解析准备 ===
if stmt.Model == nil {
stmt.Model = stmt.Dest
} else if stmt.Dest == nil {
stmt.Dest = stmt.Model
}

// === 第 7 阶段: 解析 Schema ===
if stmt.Model != nil {
if err := stmt.Parse(stmt.Model); err != nil {
db.AddError(err)
}
}

// === 第 8 阶段: 准备 ReflectValue ===
if stmt.Dest != nil {
stmt.ReflectValue = reflect.ValueOf(stmt.Dest)
for stmt.ReflectValue.Kind() == reflect.Ptr {
if stmt.ReflectValue.IsNil() && stmt.ReflectValue.CanAddr() {
stmt.ReflectValue.Set(reflect.New(stmt.ReflectValue.Type().Elem()))
}
stmt.ReflectValue = stmt.ReflectValue.Elem()
}
if !stmt.ReflectValue.IsValid() {
db.AddError(ErrInvalidValue)
}
}

// === 第 9 阶段: 执行回调链 ===
for _, f := range p.fns {
f(db)
}

// === 第 10 阶段: 日志记录 ===
if stmt.SQL.Len() > 0 {
db.Logger.Trace(stmt.Context, curTime, func() (string, int64) {
sql, vars := stmt.SQL.String(), stmt.Vars
if filter, ok := db.Logger.(ParamsFilter); ok {
sql, vars = filter.ParamsFilter(stmt.Context, stmt.SQL.String(), stmt.Vars...)
}
return db.Dialector.Explain(sql, vars...), db.RowsAffected
}, db.Error)
}

// === 第 11 阶段: 清理 ===
if !stmt.DB.DryRun {
stmt.SQL.Reset()
stmt.Vars = nil
}

if resetBuildClauses {
stmt.BuildClauses = nil
}

return db
}

流程图:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
┌─────────────────────────────────────────────────────────────┐
│ processor.Execute() │
└─────────────────────────────────────────────────────────────┘


┌────────────────────────────────────────────────────┐
│ 第 1 阶段: 执行 Scopes │
│ for len(db.Statement.scopes) > 0 { │
│ db = db.executeScopes() │
│ } │
└────────────────────────────────────────────────────┘


┌────────────────────────────────────────────────────┐
│ 第 2 阶段: 初始化 │
│ curTime = time.Now() │
│ stmt = db.Statement │
│ resetBuildClauses = false │
└────────────────────────────────────────────────────┘


┌────────────────────────────────────────────────────┐
│ 第 3 阶段: 配置 BuildClauses │
│ if len(stmt.BuildClauses) == 0 { │
│ stmt.BuildClauses = p.Clauses │
│ resetBuildClauses = true │
│ } │
└────────────────────────────────────────────────────┘


┌────────────────────────────────────────────────────┐
│ 第 4 阶段: StatementModifier │
│ if optimizer, ok := stmt.Dest.(StatementModifier) │
│ optimizer.ModifyStatement(stmt) │
└────────────────────────────────────────────────────┘


┌────────────────────────────────────────────────────┐
│ 第 5 阶段: 超时设置 │
│ if db.DefaultContextTimeout > 0 { │
│ stmt.Context, _ = context.WithTimeout(...) │
│ } │
└────────────────────────────────────────────────────┘


┌────────────────────────────────────────────────────┐
│ 第 6 阶段: 模型解析准备 │
│ if stmt.Model == nil { │
│ stmt.Model = stmt.Dest │
│ } else if stmt.Dest == nil { │
│ stmt.Dest = stmt.Model │
│ } │
└────────────────────────────────────────────────────┘


┌────────────────────────────────────────────────────┐
│ 第 7 阶段: 解析 Schema │
│ if stmt.Model != nil { │
│ stmt.Parse(stmt.Model) │
│ } │
└────────────────────────────────────────────────────┘


┌────────────────────────────────────────────────────┐
│ 第 8 阶段: 准备 ReflectValue │
│ if stmt.Dest != nil { │
│ stmt.ReflectValue = reflect.ValueOf(...) │
│ // 解引用指针 │
│ // 检查有效性 │
│ } │
└────────────────────────────────────────────────────┘


┌────────────────────────────────────────────────────┐
│ 第 9 阶段: 执行回调链 ★核心★ │
│ for _, f := range p.fns { │
│ f(db) // 执行每个回调 │
│ } │
└────────────────────────────────────────────────────┘


┌────────────────────────────────────────────────────┐
│ 第 10 阶段: 日志记录 │
│ if stmt.SQL.Len() > 0 { │
│ db.Logger.Trace(...) │
│ } │
└────────────────────────────────────────────────────┘


┌────────────────────────────────────────────────────┐
│ 第 11 阶段: 清理 │
│ if !stmt.DB.DryRun { │
│ stmt.SQL.Reset() │
│ stmt.Vars = nil │
│ } │
│ if resetBuildClauses { │
│ stmt.BuildClauses = nil │
│ } │
└────────────────────────────────────────────────────┘


return db

概念 4: 回调排序算法

定义: sortCallbacks 使用拓扑排序确保回调顺序正确。

算法详解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
func sortCallbacks(cs []*callback) (fns []func(*DB), err error) {
var (
names, sorted []string
sortCallback func(*callback) error
}

// === 第 1 步: 特殊排序 ===
// before="*" 的排在最前,after="*" 的排在最后
sort.SliceStable(cs, func(i, j int) bool {
if cs[j].before == "*" && cs[i].before != "*" {
return true
}
if cs[j].after == "*" && cs[i].after != "*" {
return true
}
return false
})

// === 第 2 步: 收集名称并检测重复 ===
for _, c := range cs {
if idx := getRIndex(names, c.name); idx > -1 && !c.replace && !c.remove {
// 警告: 重复的回调名称
c.processor.db.Logger.Warn(context.Background(),
"duplicated callback `%s` from %s\n", c.name, utils.FileWithLineNum())
}
names = append(names, c.name)
}

// === 第 3 步: 递归排序函数 ===
sortCallback = func(c *callback) error {
// 处理 before 关系
if c.before != "" {
if c.before == "*" { // 排在最前
if len(sorted) > 0 {
sorted = append([]string{c.name}, sorted...)
}
} else if sortedIdx := getRIndex(sorted, c.before); sortedIdx != -1 {
// 目标已排序,插入到目标前面
if curIdx := getRIndex(sorted, c.name); curIdx == -1 {
sorted = append(sorted[:sortedIdx],
append([]string{c.name}, sorted[sortedIdx:]...)...)
} else if curIdx > sortedIdx {
return fmt.Errorf("conflicting callback %s with before %s", c.name, c.before)
}
} else if idx := getRIndex(names, c.before); idx != -1 {
// 目标未排序,设置目标 after 当前
cs[idx].after = c.name
}
}

// 处理 after 关系
if c.after != "" {
if c.after == "*" { // 排在最后
sorted = append(sorted, c.name)
} else if sortedIdx := getRIndex(sorted, c.after); sortedIdx != -1 {
// 目标已排序,追加到末尾
if curIdx := getRIndex(sorted, c.name); curIdx == -1 {
sorted = append(sorted, c.name)
} else if curIdx < sortedIdx {
return fmt.Errorf("conflicting callback %s with before %s", c.name, c.after)
}
} else if idx := getRIndex(names, c.after); idx != -1 {
// 目标未排序,设置目标 before 当前
after := cs[idx]
if after.before == "" {
after.before = c.name
}
if err := sortCallback(after); err != nil {
return err
}
return sortCallback(c)
}
}

// 如果未排序,追加到末尾
if getRIndex(sorted, c.name) == -1 {
sorted = append(sorted, c.name)
}

return nil
}

// === 第 4 步: 对所有回调排序 ===
for _, c := range cs {
if err = sortCallback(c); err != nil {
return
}
}

// === 第 5 步: 生成函数列表 ===
for _, name := range sorted {
if idx := getRIndex(names, name); !cs[idx].remove {
fns = append(fns, cs[idx].handler)
}
}

return
}

排序示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
注册顺序:
1. Register("A", fnA)
2. Register("B", fnB).After("A")
3. Register("C", fnC).Before("A")
4. Register("D", fnD).After("B")
5. Register("E", fnE).Before("*") // 最前
6. Register("F", fnF).After("*") // 最后

排序过程:
初始: []
排序 A: [A]
排序 B: [A, B]
排序 C: [C, A, B] (C 在 A 之前)
排序 D: [C, A, B, D] (D 在 B 之后)
排序 E: [E, C, A, B, D] (E 最前)
排序 F: [E, C, A, B, D, F] (F 最后)

最终执行顺序: E → C → A → B → D → F

概念 5: 模型钩子系统

定义: 模型钩子是定义在结构体上的特殊方法,在特定操作时自动调用。

钩子接口定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 创建钩子
type BeforeCreateInterface interface {
BeforeCreate(*DB) error
}

type AfterCreateInterface interface {
AfterCreate(*DB) error
}

// 查询钩子
type AfterFindInterface interface {
AfterFind(*DB) error
}

// 更新钩子
type BeforeUpdateInterface interface {
BeforeUpdate(*DB) error
}

type AfterUpdateInterface interface {
AfterUpdate(*DB) error
}

// 删除钩子
type BeforeDeleteInterface interface {
BeforeDelete(*DB) error
}

type AfterDeleteInterface interface {
AfterDelete(*DB) error
}

钩子执行机制:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// callMethod 调用模型钩子
func callMethod(db *DB, fc func(value interface{}, tx *DB) bool) {
if db.Statement.Schema != nil {
callMethodValue := func(reflectValue reflect.Value) {
for _, fc := range []func(*DB){
// BeforeCreate
func(db *DB) {
if i, ok := db.Statement.Dest.(BeforeCreateInterface); ok {
db.AddError(i.BeforeCreate(db))
}
},
// AfterCreate
func(db *DB) {
if i, ok := db.Statement.Dest.(AfterCreateInterface); ok {
db.AddError(i.AfterCreate(db))
}
},
// ... 其他钩子
} {
fc(db)
}
}
callMethodValue(db.Statement.ReflectValue)
}
}

钩子执行顺序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
Create 操作:
1. BeginTransaction
2. BeforeCreate (全局回调)
3. BeforeCreate (模型钩子) ← 调用 user.BeforeCreate()
4. SaveBeforeAssociations
5. Create (执行 INSERT)
6. SaveAfterAssociations
7. AfterCreate (模型钩子) ← 调用 user.AfterCreate()
8. AfterCreate (全局回调)
9. CommitOrRollbackTransaction

Query 操作:
1. Query (执行 SELECT)
2. Preload
3. AfterFind (模型钩子) ← 调用 user.AfterFind()
4. AfterQuery (全局回调)

Update 操作:
1. BeginTransaction
2. BeforeUpdate (全局回调)
3. BeforeUpdate (模型钩子) ← 调用 user.BeforeUpdate()
4. SaveBeforeAssociations
5. Update (执行 UPDATE)
6. SaveAfterAssociations
7. AfterUpdate (模型钩子) ← 调用 user.AfterUpdate()
8. AfterUpdate (全局回调)
9. CommitOrRollbackTransaction

Delete 操作:
1. BeginTransaction
2. BeforeDelete (全局回调)
3. BeforeDelete (模型钩子) ← 调用 user.BeforeDelete()
4. DeleteBeforeAssociations
5. Delete (执行 DELETE)
6. AfterDelete (模型钩子) ← 调用 user.AfterDelete()
7. AfterDelete (全局回调)
8. CommitOrRollbackTransaction

2.2 理论基础

理论 1: 责任链模式

模式定义: 为请求创建一个接收者对象链,每个接收者都包含对下一个接收者的引用。

在回调系统中的体现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
回调链结构:

┌────────────────────────────────────────────────────────────┐
│ 回调链 (责任链) │
└────────────────────────────────────────────────────────────┘

回调1 回调2 回调3 回调4
[validate] → [sanitize] → [set_default] → [log]
│ │ │ │
▼ ▼ ▼ ▼
检查输入 清理数据 设置默认值 记录日志
│ │ │ │
└──────────────┴───────────────┴───────────────┘


核心操作 (SQL)


回调5 → 回调6 → 回调7
[audit] → [cache] → [notify]
│ │ │
▼ ▼ ▼
审计日志 更新缓存 发送通知

代码实现:

1
2
3
4
5
6
7
8
// 回调链执行
for _, f := range p.fns {
f(db) // 每个 f 都是一个处理器
if db.Error != nil {
// 可以中断链
return
}
}

优势:

  1. 解耦: 每个回调独立,互不依赖
  2. 灵活: 可以动态添加/移除回调
  3. 可扩展: 新增功能只需注册新回调
  4. 可测试: 每个回调可以独立测试

中断链:

1
2
3
4
5
6
7
// 返回错误可以中断链
db.Callback().Create().Register("validate", func(db *gorm.DB) {
if user.Name == "" {
db.AddError(errors.New("name is required"))
return // 这将中断后续回调
}
})

理论 2: 事件驱动模式

模式定义: 当对象的状态改变时,自动通知依赖它的对象。

在回调系统中的体现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
事件流:

用户操作 触发事件 事件处理
│ │ │
▼ ▼ ▼
db.Create(&user) → "before:create" → 执行 BeforeCreate 回调
│ │ │
│ ▼ ▼
│ "create" → 执行 Create 回调
│ │ │
│ ▼ ▼
│ "after:create" → 执行 AfterCreate 回调
│ │ │
└───────────────────────┴───────────────────────┘


操作完成

事件类型:

事件类型 时机 用途
before:create 创建前 验证、设置默认值
create 创建时 执行 INSERT
after:create 创建后 更新关联、清理
before:query 查询前 修改查询条件
query 查询时 执行 SELECT
after:query 查询后 数据转换、缓存
before:update 更新前 验证、记录旧值
update 更新时 执行 UPDATE
after:update 更新后 更新关联、通知
before:delete 删除前 检查依赖、备份
delete 删除时 执行 DELETE
after:delete 删除后 清理关联、通知

事件驱动实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 事件发布 (隐式)
func (db *DB) Create(value interface{}) *DB {
// ...
return db.callbacks.Create().Execute(db)
}

// 事件订阅
db.Callback().Create().Before("gorm:create").Register("validate", func(db *gorm.DB) {
// 订阅 before:create 事件
})

db.Callback().Create().After("gorm:create").Register("log", func(db *gorm.DB) {
// 订阅 after:create 事件
})

理论 3: 拓扑排序

定义: 对有向无环图 (DAG) 的顶点进行排序,使得对于每一条有向边 (u, v),顶点 u 在排序中都在顶点 v 之前。

在回调排序中的应用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
回调依赖图:

C (before A)


A


B (after A)


D (after B)

拓扑排序结果: C → A → B → D

有环情况检测:
A before B
B before C
C before A ← 环!

错误: conflicting callback A with before B

算法特点:

  1. 依赖优先: before 优先级高,after 优先级低
  2. 冲突检测: 检测循环依赖并报错
  3. 特殊处理: * 表示最前/最后

代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 递归排序
sortCallback = func(c *callback) error {
// 处理 before 关系 (前置依赖)
if c.before != "" {
// ...
}

// 处理 after 关系 (后置依赖)
if c.after != "" {
// ...
}

// 如果未排序,追加到末尾
if getRIndex(sorted, c.name) == -1 {
sorted = append(sorted, c.name)
}

return nil
}

// 对所有回调排序
for _, c := range cs {
if err = sortCallback(c); err != nil {
return // 冲突检测失败
}
}

理论 4: 观察者模式

模式定义: 定义对象间的一对多依赖关系,当一个对象状态改变时,所有依赖它的对象都会得到通知并自动更新。

在模型钩子中的体现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
观察者模式应用:

Subject (被观察者) Observer (观察者)
┌─────────────┐ ┌─────────────┐
│ User │ │ Logger │
│ (模型) │ notify → │ (日志回调) │
└─────────────┘ └─────────────┘
│ │
│ notify │ update
▼ ▼
┌─────────────┐ ┌─────────────┐
│ Cache │ │ Auditor │
│ (缓存回调) │ notify → │ (审计回调) │
└─────────────┘ └─────────────┘

接口定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 观察者接口
type BeforeCreateInterface interface {
BeforeCreate(*DB) error // 通知方法
}

// 被观察者 (隐式)
type User struct {
gorm.Model
Name string
}

// 观察者实现
func (u *User) BeforeCreate(tx *gorm.DB) error {
// 当 User 被创建时,此方法被调用
log.Printf("Creating user: %s", u.Name)
return nil
}

2.3 学习方法

方法 1: 追踪法

步骤: 从 Finisher API 开始,追踪整个回调执行流程。

示例 - 查询流程追踪:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
1. 用户调用
db.Find(&users)

2. 进入 finisher_api.go
func (db *DB) Find(dest interface{}, conds ...interface{}) *DB {
tx := db.getInstance()
tx.Statement.Dest = dest
// ...
return tx.callbacks.Query().Execute(tx) ← 关键调用
}

3. 进入 callbacks.go
func (p *processor) Execute(db *DB) *DB {
// 执行 Scopes
for len(db.Statement.scopes) > 0 {
db = db.executeScopes()
}

// 配置 BuildClauses
if len(stmt.BuildClauses) == 0 {
stmt.BuildClauses = p.Clauses
}

// 解析模型
if stmt.Model != nil {
stmt.Parse(stmt.Model)
}

// 执行回调链
for _, f := range p.fns {
f(db) ← 执行每个回调
}

// 记录日志
if stmt.SQL.Len() > 0 {
db.Logger.Trace(...)
}

return db
}

4. 默认查询回调
- gorm:query → BuildQuerySQL + 执行查询
- gorm:preload → 预加载关联
- gorm:after_query → AfterFind 钩子

5. query.go 中的 Query 回调
func Query(db *gorm.DB) {
if db.Error == nil {
BuildQuerySQL(db) ← 构建 SQL

if !db.DryRun && db.Error == nil {
rows, err := db.Statement.ConnPool.QueryContext(...)
gorm.Scan(rows, db, 0) ← 扫描结果
}
}
}

方法 2: 对比法

对比模型钩子和全局回调:

特性 模型钩子 全局回调
定义位置 结构体方法 注册到 DB
作用范围 只影响该模型 影响所有操作
灵活性 低(绑定模型) 高(可条件执行)
性能 需要类型断言 直接函数调用
适用场景 模型特定逻辑 通用逻辑(日志、验证)

对比示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 模型钩子 - 只影响 User
type User struct {
gorm.Model
Name string
}

func (u *User) BeforeCreate(tx *gorm.DB) error {
u.Name = strings.TrimSpace(u.Name)
return nil
}

// 全局回调 - 影响所有创建操作
db.Callback().Create().Before("gorm:create").Register("validate", func(tx *gorm.DB) {
if tx.Statement.Schema != nil {
// 通用验证逻辑
}
})

// 条件回调 - 只在特定条件下执行
db.Callback().Create().Match(func(db *gorm.DB) bool {
return !db.SkipDefaultTransaction
}).Register("begin_transaction", beginTx)

对比 Before 和 After:

1
2
3
4
5
6
7
8
9
10
11
12
13
// Before: 在核心操作之前
db.Callback().Create().Before("gorm:create").Register("validate", func(db *gorm.DB) {
// 可以修改数据
if user.Name == "" {
db.AddError(errors.New("name required"))
}
})

// After: 在核心操作之后
db.Callback().Create().After("gorm:create").Register("log", func(db *gorm.DB) {
// 可以访问结果
log.Printf("Created user with ID: %d", user.ID)
})

方法 3: 实践法

实践 1: 实现审计日志

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// 目标: 记录所有数据变更

type AuditLog struct {
ID uint
Action string // create/update/delete
TableName string
RecordID uint
OldData string
NewData string
CreatedAt time.Time
}

// 注册审计回调
db.Callback().Create().After("gorm:create").Register("audit:create", func(tx *gorm.DB) {
if tx.Error == nil && tx.Statement.Schema != nil {
data, _ := json.Marshal(tx.Statement.Dest)
tx.Create(&AuditLog{
Action: "create",
TableName: tx.Statement.Table,
RecordID: tx.Statement.ReflectValue.FieldByName("ID").Uint(),
NewData: string(data),
})
}
})

db.Callback().Update().After("gorm:update").Register("audit:update", func(tx *gorm.DB) {
if tx.Error == nil && tx.Statement.Schema != nil {
oldData, _ := json.Marshal(tx.Statement.Dest)
newData, _ := json.Marshal(tx.Statement.ReflectValue.Interface())
tx.Create(&AuditLog{
Action: "update",
TableName: tx.Statement.Table,
OldData: string(oldData),
NewData: string(newData),
})
}
})

实践 2: 实现软删除

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 目标: 删除时标记删除,而非物理删除

type SoftDelete struct {
DeletedAt gorm.DeletedAt `gorm:"index"`
}

// 注册软删除回调
db.Callback().Delete().Before("gorm:delete").Register("soft_delete", func(tx *gorm.DB) {
if tx.Statement.Schema != nil {
// 检查是否有 DeletedAt 字段
if field := tx.Statement.Schema.LookUpField("DeletedAt"); field != nil {
// 修改为更新操作
tx.Statement.DB = tx.Model(tx.Statement.Dest).Update("DeletedAt", time.Now())
// 跳过实际的删除
tx.SkipHooks = true
}
}
})

实践 3: 实现条件验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 目标: 只对特定模型进行验证

type Validatable interface {
Validate() error
}

// 注册验证回调
db.Callback().Create().Before("gorm:create").Register("validate", func(tx *gorm.DB) {
if tx.Statement.Schema != nil && !tx.SkipHooks {
// 检查是否实现了 Validatable 接口
if v, ok := tx.Statement.Dest.(Validatable); ok {
if err := v.Validate(); err != nil {
tx.AddError(err)
}
}
}
})

// 使用
type User struct {
gorm.Model
Name string
Email string
}

func (u *User) Validate() error {
if u.Name == "" {
return errors.New("name is required")
}
if u.Email == "" {
return errors.New("email is required")
}
return nil
}

2.4 实施策略

策略 1: 分层学习

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
第 1 层: 理解回调基础 (Day 1)
目标: 理解回调系统的基本概念
内容:
- callbacks 和 processor 结构
- 回调注册方法
- 默认回调列表
验证:
- 能列出 6 种处理器类型
- 能解释回调的执行顺序
- 能注册简单回调

第 2 层: 掌握回调机制 (Day 2)
目标: 理解回调如何被执行
内容:
- processor.Execute() 流程
- 回调排序算法
- 回调链执行
验证:
- 能画出 Execute 流程图
- 能解释排序算法
- 能追踪回调执行

第 3 层: 应用回调技巧 (Day 3)
目标: 掌握高级回调用法
内容:
- 模型钩子系统
- 条件回调
- 回调最佳实践
验证:
- 能实现模型钩子
- 能实现条件回调
- 能排查回调问题

策略 2: 问题驱动

问题序列:

  1. 为什么需要回调系统?

    • 尝试直接修改 GORM 源码添加功能
    • 体验升级时的困难
    • 理解回调系统的价值
  2. 回调是如何被触发的?

    • 设置断点在 Find() 方法
    • 追踪到 Execute()
    • 观察回调链执行
  3. 如何保证回调顺序?

    • 注册多个有依赖关系的回调
    • 观察排序结果
    • 理解 before/after 机制
  4. 模型钩子和全局回调有什么区别?

    • 对比两种方式的实现
    • 测试性能差异
    • 选择合适的场景
  5. 如何实现条件回调?

    • 实现 Match 条件
    • 测试不同情况
    • 理解 compile 过程

策略 3: 验证反馈

验证点 1: 理解验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 测试: 回调执行顺序
func TestCallbackOrder(t *testing.T) {
var order []string

db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{})
db.Callback().Create().Register("A", func(db *gorm.DB) {
order = append(order, "A")
})
db.Callback().Create().Register("B", func(db *gorm.DB) {
order = append(order, "B")
}).After("A")
db.Callback().Create().Register("C", func(db *gorm.DB) {
order = append(order, "C")
}).Before("A")

db.Create(&User{})

assert.Equal(t, []string{"C", "A", "B"}, order)
}

验证点 2: 实践验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// 任务: 实现完整的审计系统

type AuditService struct {
db *gorm.DB
}

func (s *AuditService) Register(db *gorm.DB) {
// Create 审计
db.Callback().Create().After("gorm:create").Register("audit:create", s.logCreate)

// Update 审计
db.Callback().Update().After("gorm:update").Register("audit:update", s.logUpdate)

// Delete 审计
db.Callback().Delete().After("gorm:delete").Register("audit:delete", s.logDelete)
}

func (s *AuditService) logCreate(tx *gorm.DB) {
// 实现创建审计
}

func (s *AuditService) logUpdate(tx *gorm.DB) {
// 实现更新审计
}

func (s *AuditService) logDelete(tx *gorm.DB) {
// 实现删除审计
}

// 测试
func TestAuditService(t *testing.T) {
db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{})
service := &AuditService{db: db}
service.Register(db)

// 执行操作
db.Create(&User{Name: "John"})

// 验证审计日志
var logs []AuditLog
db.Find(&logs)
assert.Equal(t, 1, len(logs))
assert.Equal(t, "create", logs[0].Action)
}

三、学习路径建议

3.1 前置知识检查

知识点 要求 检验方式
接口定义 理解接口和方法集 能定义 BeforeCreateInterface
函数类型 理解函数作为值 能写出 func(*DB) 的签名
拓扑排序 了解 DAG 排序 能手动排序简单依赖
设计模式 了解责任链、观察者 能识别应用场景

3.2 学习时间分配

内容 理论 实践 产出
Day 1: 回调基础 2h 1.5h 回调注册示例
Day 2: 执行流程 1.5h 2h 流程图、追踪日志
Day 3: 高级特性 1.5h 2h 审计系统、软删除

3.3 学习成果验收

理论验收:

  • 能解释回调系统的设计目的
  • 能说明 processor.Execute() 的流程
  • 能区分模型钩子和全局回调
  • 能解释回调排序算法

实践验收:

  • 能注册全局回调
  • 能实现模型钩子
  • 能实现条件回调
  • 能排查回调顺序问题

综合验收:

  • 能实现完整的审计系统
  • 能分析回调执行顺序
  • 能优化回调性能
  • 能向他人讲解回调系统

四、callbacks 结构完整解析 (callbacks.go:29-73)

callbacks 和 processor 结构体

完整定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// callbacks 回调容器,管理所有操作的处理器
type callbacks struct {
processors map[string]*processor // 处理器映射
}

// processor 单个操作的处理器
type processor struct {
db *gorm.DB // DB 实例
Clauses []string // 子句构建顺序
fns []func(*gorm.DB) // 编译后的函数列表
callbacks []*callback // 回调列表
}

// callback 单个回调的定义
type callback struct {
name string // 回调名称: "gorm:before_create"
before string // 在哪个回调之前执行
after string // 在哪个回调之后执行
remove bool // 是否移除
replace bool // 是否替换
match func(*gorm.DB) bool // 匹配条件函数
handler func(*gorm.DB) // 处理函数
processor *processor // 所属处理器
}

处理器访问方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// Create 获取 Create 处理器
func (cs *callbacks) Create() *processor {
return cs.processors["create"]
}

// Query 获取 Query 处理器
func (cs *callbacks) Query() *processor {
return cs.processors["query"]
}

// Update 获取 Update 处理器
func (cs *callbacks) Update() *processor {
return cs.processors["update"]
}

// Delete 获取 Delete 处理器
func (cs *callbacks) Delete() *processor {
return cs.processors["delete"]
}

// Row 获取 Row 处理器
func (cs *callbacks) Row() *processor {
return cs.processors["row"]
}

// Raw 获取 Raw 处理器
func (cs *callbacks) Raw() *processor {
return cs.processors["raw"]
}

RegisterDefaultCallbacks 完整实现 (callbacks.go:22-83)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
// Config 回调配置
type Config struct {
LastInsertIDReversed bool // 是否反转 LastInsertID 顺序
CreateClauses []string // Create 操作的子句顺序
QueryClauses []string // Query 操作的子句顺序
UpdateClauses []string // Update 操作的子句顺序
DeleteClauses []string // Delete 操作的子句顺序
}

// 默认子句顺序
var (
createClauses = []string{"INSERT", "VALUES", "ON CONFLICT"}
queryClauses = []string{"SELECT", "FROM", "WHERE", "GROUP BY", "ORDER BY", "LIMIT", "FOR"}
updateClauses = []string{"UPDATE", "SET", "WHERE"}
deleteClauses = []string{"DELETE", "FROM", "WHERE"}
)

// RegisterDefaultCallbacks 注册默认回调
func RegisterDefaultCallbacks(db *gorm.DB, config *Config) {
// 1. 定义事务启用条件
enableTransaction := func(db *gorm.DB) bool {
return !db.SkipDefaultTransaction
}

// 2. 设置默认子句顺序(如果未指定)
if len(config.CreateClauses) == 0 {
config.CreateClauses = createClauses
}
if len(config.QueryClauses) == 0 {
config.QueryClauses = queryClauses
}
if len(config.DeleteClauses) == 0 {
config.DeleteClauses = deleteClauses
}
if len(config.UpdateClauses) == 0 {
config.UpdateClauses = updateClauses
}

// 3. 注册 Create 回调链
createCallback := db.Callback().Create()
createCallback.Match(enableTransaction).Register("gorm:begin_transaction", BeginTransaction)
createCallback.Register("gorm:before_create", BeforeCreate)
createCallback.Register("gorm:save_before_associations", SaveBeforeAssociations(true))
createCallback.Register("gorm:create", Create(config))
createCallback.Register("gorm:save_after_associations", SaveAfterAssociations(true))
createCallback.Register("gorm:after_create", AfterCreate)
createCallback.Match(enableTransaction).Register("gorm:commit_or_rollback_transaction", CommitOrRollbackTransaction)
createCallback.Clauses = config.CreateClauses

// 4. 注册 Query 回调链
queryCallback := db.Callback().Query()
queryCallback.Register("gorm:query", Query)
queryCallback.Register("gorm:preload", Preload)
queryCallback.Register("gorm:after_query", AfterQuery)
queryCallback.Clauses = config.QueryClauses

// 5. 注册 Delete 回调链
deleteCallback := db.Callback().Delete()
deleteCallback.Match(enableTransaction).Register("gorm:begin_transaction", BeginTransaction)
deleteCallback.Register("gorm:before_delete", BeforeDelete)
deleteCallback.Register("gorm:delete_before_associations", DeleteBeforeAssociations)
deleteCallback.Register("gorm:delete", Delete(config))
deleteCallback.Register("gorm:after_delete", AfterDelete)
deleteCallback.Match(enableTransaction).Register("gorm:commit_or_rollback_transaction", CommitOrRollbackTransaction)
deleteCallback.Clauses = config.DeleteClauses

// 6. 注册 Update 回调链
updateCallback := db.Callback().Update()
updateCallback.Match(enableTransaction).Register("gorm:begin_transaction", BeginTransaction)
updateCallback.Register("gorm:setup_reflect_value", SetupUpdateReflectValue)
updateCallback.Register("gorm:before_update", BeforeUpdate)
updateCallback.Register("gorm:save_before_associations", SaveBeforeAssociations(false))
updateCallback.Register("gorm:update", Update(config))
updateCallback.Register("gorm:save_after_associations", SaveAfterAssociations(false))
updateCallback.Register("gorm:after_update", AfterUpdate)
updateCallback.Match(enableTransaction).Register("gorm:commit_or_rollback_transaction", CommitOrRollbackTransaction)
updateCallback.Clauses = config.UpdateClauses

// 7. 注册 Row 回调链
rowCallback := db.Callback().Row()
rowCallback.Register("gorm:row", RowQuery)
rowCallback.Clauses = config.QueryClauses

// 8. 注册 Raw 回调链
rawCallback := db.Callback().Raw()
rawCallback.Register("gorm:raw", RawExec)
rawCallback.Clauses = config.QueryClauses
}

Create 回调流程完整解析 (create.go:14-100+)

BeforeCreate 回调:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// BeforeCreate 创建前的模型钩子
func BeforeCreate(db *gorm.DB) {
// 1. 检查执行条件
if db.Error == nil &&
db.Statement.Schema != nil &&
!db.Statement.SkipHooks &&
(db.Statement.Schema.BeforeSave || db.Statement.Schema.BeforeCreate) {

// 2. 调用模型方法
callMethod(db, func(value interface{}, tx *gorm.DB) (called bool) {
// 2.1 先调用 BeforeSave
if db.Statement.Schema.BeforeSave {
if i, ok := value.(BeforeSaveInterface); ok {
called = true
db.AddError(i.BeforeSave(tx))
}
}

// 2.2 再调用 BeforeCreate
if db.Statement.Schema.BeforeCreate {
if i, ok := value.(BeforeCreateInterface); ok {
called = true
db.AddError(i.BeforeCreate(tx))
}
}
return called
})
}
}

Create 核心回调:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
// Create 创建回调的核心逻辑
func Create(config *Config) func(db *gorm.DB) {
supportReturning := utils.Contains(config.CreateClauses, "RETURNING")

return func(db *gorm.DB) {
// 1. 检查错误状态
if db.Error != nil {
return
}

// 2. 添加 Schema 级别的子句
if db.Statement.Schema != nil {
if !db.Statement.Unscoped {
for _, c := range db.Statement.Schema.CreateClauses {
db.Statement.AddClause(c)
}
}

// 2.1 处理 RETURNING 子句
if supportReturning && len(db.Statement.Schema.FieldsWithDefaultDBValue) > 0 {
if _, ok := db.Statement.Clauses["RETURNING"]; !ok {
fromColumns := make([]clause.Column, 0,
len(db.Statement.Schema.FieldsWithDefaultDBValue))
for _, field := range db.Statement.Schema.FieldsWithDefaultDBValue {
if field.Readable {
fromColumns = append(fromColumns, clause.Column{Name: field.DBName})
}
}
if len(fromColumns) > 0 {
db.Statement.AddClause(clause.Returning{Columns: fromColumns})
}
}
}
}

// 3. 构建 SQL
if db.Statement.SQL.Len() == 0 {
db.Statement.SQL.Grow(180)
db.Statement.AddClauseIfNotExists(clause.Insert{})
db.Statement.AddClause(ConvertToCreateValues(db.Statement))
db.Statement.Build(db.Statement.BuildClauses...)
}

// 4. 执行 SQL (非 DryRun 模式)
if !db.DryRun && db.Error == nil {
return
}

// 5. 处理 RETURNING 结果
ok, mode := hasReturning(db, supportReturning)
if ok {
// 5.1 处理 ON CONFLICT
if c, ok := db.Statement.Clauses["ON CONFLICT"]; ok {
onConflict, _ := c.Expression.(clause.OnConflict)
if onConflict.DoNothing {
mode |= gorm.ScanOnConflictDoNothing
} else if len(onConflict.DoUpdates) > 0 || onConflict.UpdateAll {
mode |= gorm.ScanUpdate
}
}

// 5.2 执行查询并扫描结果
rows, err := db.Statement.ConnPool.QueryContext(
db.Statement.Context,
db.Statement.SQL.String(),
db.Statement.Vars...,
)
if db.AddError(err) == nil {
defer func() {
db.AddError(rows.Close())
}()
gorm.Scan(rows, db, mode)

// 5.3 处理结果
if db.Statement.Result != nil {
// ... 处理结果
}
}
}
}
}

回调注册 API 完整解析

回调注册方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
// Register 注册回调
func (p *processor) Register(name string, handler func(*gorm.DB)) *callback {
// 1. 创建回调实例
f := &callback{
name: name,
handler: handler,
processor: p,
}

// 2. 添加到回调列表
p.callbacks = append(p.callbacks, f)

// 3. 清空编译的函数列表(需要重新编译)
p.fns = nil

return f
}

// Before 在指定回调之前注册
func (f *callback) Before(name string) *callback {
f.before = name
return f
}

// After 在指定回调之后注册
func (f *callback) After(name string) *callback {
f.after = name
return f
}

// Match 设置匹配条件
func (f *callback) Match(match func(*gorm.DB) bool) *callback {
f.match = match
return f
}

// Replace 替换指定回调
func (p *processor) Replace(name string, handler func(*gorm.DB)) *callback {
// 1. 查找目标回调
for _, callback := range p.callbacks {
if callback.name == name {
callback.replace = true
callback.handler = handler
break
}
}

// 2. 清空编译的函数列表
p.fns = nil

return nil
}

// Remove 移除指定回调
func (p *processor) Remove(name string) {
// 1. 标记要移除的回调
for _, callback := range p.callbacks {
if callback.name == name {
callback.remove = true
break
}
}

// 2. 清空编译的函数列表
p.fns = nil
}

回调执行流程图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
processor.Execute(db)


[编译回调函数]
fns == nil? ──Yes──► 编译回调列表
│ No │
▼ │
[遍历函数列表] │
for _, fn := range fns

├─► 执行 fn(db)

├─► 检查错误
│ db.Error != nil? ──Yes──► 停止执行
│ │ No
│ ▼
└─► 继续


返回 db

---

回调编译流程:

compileCallbacks()


[收集有效回调]
for _, callback := range callbacks

├─► 跳过已移除 (callback.remove)

├─► 跳过被替换 (callback.replace)

└─► 添加到列表


[拓扑排序]
根据依赖关系排序
before/after 关系


[生成函数列表]
fns = [fn1, fn2, fn3, ...]


返回

五、实战代码示例

5.1 基础回调使用

示例 1: 注册全局回调

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package main

import (
"fmt"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)

type User struct {
ID uint
Name string
Email string
}

func main() {
db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{})

// 1. 注册 Before 回调
db.Callback().Create().Before("gorm:create").Register("my:before_create", func(db *gorm.DB) {
fmt.Println("Before create:", db.Statement.Statement)
})

// 2. 注册 After 回调
db.Callback().Create().After("gorm:create").Register("my:after_create", func(db *gorm.DB) {
fmt.Println("After create:", db.Statement.RowsAffected)
})

// 3. 执行创建
db.Create(&User{Name: "John", Email: "john@example.com"})
// 输出:
// Before create: ...
// After create: 1
}

示例 2: 条件回调

1
2
3
4
5
6
7
// 只在特定条件下执行
db.Callback().Create().Match(func(db *gorm.DB) bool {
// 只在非事务模式下执行
return db.SkipDefaultTransaction
}).Register("my:conditional_callback", func(db *gorm.DB) {
fmt.Println("Conditional callback executed")
})

示例 3: 替换默认回调

1
2
3
4
5
6
7
8
9
10
11
// 替换默认的创建回调
db.Callback().Create().Replace("gorm:create", func(db *gorm.DB) {
fmt.Println("Custom create logic")

// 自定义创建逻辑
db.Statement.SQL.WriteString("INSERT INTO users (name) VALUES (?)")
db.Statement.AddVar(db, "John")

// 执行 SQL
db.Exec(db.Statement.SQL.String(), db.Statement.Vars...)
})

5.2 模型钩子示例

示例 4: 实现模型钩子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
type User struct {
ID uint
Name string
Email string
CreatedAt time.Time
UpdatedAt time.Time
}

// BeforeCreate 创建前钩子
func (u *User) BeforeCreate(tx *gorm.DB) error {
fmt.Println("BeforeCreate hook called")

// 验证数据
if u.Name == "" {
return errors.New("name cannot be empty")
}

// 设置默认值
if u.Email == "" {
u.Email = "default@example.com"
}

return nil
}

// AfterCreate 创建后钩子
func (u *User) AfterCreate(tx *gorm.DB) error {
fmt.Println("AfterCreate hook called")

// 记录日志
fmt.Printf("User created with ID: %d\n", u.ID)

return nil
}

// BeforeUpdate 更新前钩子
func (u *User) BeforeUpdate(tx *gorm.DB) error {
fmt.Println("BeforeUpdate hook called")

// 记录变更
if tx.Statement.Changed("Name") {
fmt.Printf("Name changing from %s to %s\n",
tx.Statement.Dest.(map[string]interface{})["name_before"],
u.Name)
}

return nil
}

// 使用
db.Create(&User{Name: "John"})
// 输出:
// BeforeCreate hook called
// AfterCreate hook called
// User created with ID: 1

5.3 高级回调场景

示例 5: 实现审计日志

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
type AuditLog struct {
ID uint `gorm:"primaryKey"`
Action string `gorm:"size:50"`
TableName string `gorm:"size:50"`
RecordID uint
Timestamp time.Time
}

type AuditService struct {
db *gorm.DB
}

func (s *AuditService) Register(db *gorm.DB) {
// 创建审计
db.Callback().Create().After("gorm:after_create").Register("audit:create", s.auditCreate)

// 更新审计
db.Callback().Update().After("gorm:after_update").Register("audit:update", s.auditUpdate)

// 删除审计
db.Callback().Delete().After("gorm:after_delete").Register("audit:delete", s.auditDelete)
}

func (s *AuditService) auditCreate(db *gorm.DB) {
// 获取创建的记录 ID
if db.Statement.RowsAffected > 0 {
// 创建审计日志
s.db.Create(&AuditLog{
Action: "create",
TableName: db.Statement.Table,
RecordID: // 从结果中获取 ID
Timestamp: time.Now(),
})
}
}

func (s *AuditService) auditUpdate(db *gorm.DB) {
// 记录更新操作
}

func (s *AuditService) auditDelete(db *gorm.DB) {
// 记录删除操作
}

示例 6: 实现软删除

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
type SoftDelete struct {
DeletedAt *time.Time `gorm:"index"`
}

type User struct {
gorm.Model
SoftDelete
Name string
}

// BeforeDelete 删除前钩子
func (u *User) BeforeDelete(tx *gorm.DB) error {
// 如果是 Unscoped 操作,执行硬删除
if tx.Statement.Unscoped {
return nil
}

// 否则执行软删除
now := time.Now()
tx.Statement.SQL.WriteString("UPDATE users SET deleted_at = ? WHERE id = ? AND deleted_at IS NULL")
tx.Statement.AddVar(tx, now)
tx.Statement.AddVar(tx, u.ID)

// 跳过默认删除
tx.SkipHooks = true

return nil
}

// 使用
db.Delete(&User{}) // 软删除
db.Unscoped().Delete(&User{}) // 硬删除

六、最佳实践与故障排查

6.1 回调使用最佳实践

1. 合理使用钩子时机

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 推荐:在合适的时机执行操作
func (u *User) BeforeCreate(tx *gorm.DB) error {
// 验证数据
if err := validateEmail(u.Email); err != nil {
return err
}

// 设置默认值
if u.CreatedAt.IsZero() {
u.CreatedAt = time.Now()
}

return nil
}

// 不推荐:在钩子中执行查询
func (u *User) BeforeCreate(tx *gorm.DB) error {
// 避免在钩子中执行查询,会导致 N+1 问题
var count int64
tx.Model(&User{}).Count(&count)
return nil
}

2. 使用条件回调

1
2
3
4
5
6
7
// 只在特定条件下注册回调
db.Callback().Create().Match(func(db *gorm.DB) bool {
// 只在非 DryRun 模式下执行
return !db.DryRun
}).Register("my:dryrun_check", func(db *gorm.DB) {
// 只在非 DryRun 模式下执行
})

3. 回调错误处理

1
2
3
4
5
6
7
8
9
10
11
// 在钩子中返回错误会中断操作
func (u *User) BeforeCreate(tx *gorm.DB) error {
if u.Age < 18 {
return errors.New("age must be greater than 18")
}
return nil
}

// 使用
err := db.Create(&User{Age: 16}).Error
// Error: age must be greater than 18

6.2 常见问题与解决方案

问题 1: 回调没有被执行

症状:

1
2
3
4
5
6
func (u *User) BeforeCreate(tx *gorm.DB) error {
fmt.Println("BeforeCreate called")
return nil
}

db.Create(&User{}) // 没有输出

原因:

  1. SkipHooks 被设置为 true
  2. Schema 没有检测到钩子方法

解决方案:

1
2
3
4
5
// 检查 SkipHooks
db.Statement.SkipHooks = false

// 确保 Schema 正确解析
db.AutoMigrate(&User{})

问题 2: 回调执行顺序错误

症状: 自定义回调在默认回调之前执行

解决方案:

1
2
3
// 使用 Before/After 控制顺序
db.Callback().Create().After("gorm:before_create").Register("my:callback", fn)
db.Callback().Create().Before("gorm:create").Register("my:another", fn2)

问题 3: 钩子中的错误被忽略

症状: 钩子返回错误但操作继续执行

解决方案:

1
2
3
4
5
6
7
8
func (u *User) BeforeCreate(tx *gorm.DB) error {
if err := validate(u); err != nil {
// 确保错误被添加到 DB
tx.AddError(err)
return err // 返回错误
}
return nil
}

七、学习验证

7.1 知识自测

基础题

  1. GORM 支持哪些模型钩子?

    • A. BeforeCreate, AfterCreate
    • B. BeforeUpdate, AfterUpdate
    • C. BeforeDelete, AfterDelete
    • D. 以上都是
  2. 如何注册全局回调?

    • A. 实现 BeforeCreateInterface
    • B. 使用 db.Callback().Create().Register()
    • C. 使用 db.Callback().Create().Replace()
    • D. 以上都可以
  3. 回调的执行顺序由什么决定?

    • A. 注册顺序
    • B. before/after 关系
    • C. 回调名称字母顺序
    • D. 随机顺序

进阶题

  1. 如何让回调只在特定条件下执行?

    • 使用 Match 方法
    • 在回调函数内部判断
    • 使用 Replace 方法
    • 使用 Remove 方法
  2. 以下代码的执行顺序是什么?

    1
    2
    3
    db.Callback().Create().Register("A", fnA)
    db.Callback().Create().Register("B", fnB).After("A")
    db.Callback().Create().Register("C", fnC).Before("A")
    • A → B → C
    • C → A → B
    • B → A → C

7.2 实践练习

练习 1: 实现数据验证钩子

实现一个创建前的数据验证钩子:

1
2
3
4
5
6
7
8
func (u *User) BeforeCreate(tx *gorm.DB) error {
// TODO: 实现以下验证
// 1. Name 不能为空
// 2. Email 格式必须正确
// 3. Age 必须 >= 18

return nil
}

练习 2: 实现自定义日志回调

创建一个记录所有 SQL 操作的回调:

1
2
3
4
5
6
7
8
9
10
type QueryLogger struct {
logger *log.Logger
}

func (ql *QueryLogger) Register(db *gorm.DB) {
// TODO: 注册回调记录所有 SQL
// 1. 记录 SQL 语句
// 2. 记录执行时间
// 3. 记录影响行数
}