回调钩子模块原理说明
基于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 → 执行 → 扫描结果 │ └─────────────────────────────────────────────────────────────┘
核心价值
回调钩子模块的核心价值在于:
扩展性 : 允许在不修改核心代码的情况下插入自定义逻辑
可测试性 : 可以通过模拟钩子来测试边界情况
可维护性 : 将横切关注点(如日志、验证)与业务逻辑分离
灵活性 : 支持 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 { for len (db.Statement.scopes) > 0 { db = db.executeScopes() } var ( curTime = time.Now() stmt = db.Statement resetBuildClauses bool ) if len (stmt.BuildClauses) == 0 { stmt.BuildClauses = p.Clauses resetBuildClauses = true } if optimizer, ok := stmt.Dest.(StatementModifier); ok { optimizer.ModifyStatement(stmt) } if db.DefaultContextTimeout > 0 { if _, ok := stmt.Context.Deadline(); !ok { stmt.Context, _ = context.WithTimeout(stmt.Context, db.DefaultContextTimeout) } } if stmt.Model == nil { stmt.Model = stmt.Dest } else if stmt.Dest == nil { stmt.Dest = stmt.Model } if stmt.Model != nil { if err := stmt.Parse(stmt.Model); err != nil { db.AddError(err) } } 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) } } for _, f := range p.fns { f(db) } 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) } 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 } 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 }) 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) } sortCallback = func (c *callback) error { 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 { cs[idx].after = c.name } } 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 { 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 } for _, c := range cs { if err = sortCallback(c); err != nil { return } } 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 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) { func (db *DB) { if i, ok := db.Statement.Dest.(BeforeCreateInterface); ok { db.AddError(i.BeforeCreate(db)) } }, 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) if db.Error != nil { return } }
优势 :
解耦 : 每个回调独立,互不依赖
灵活 : 可以动态添加/移除回调
可扩展 : 新增功能只需注册新回调
可测试 : 每个回调可以独立测试
中断链 :
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) { }) db.Callback().Create().After("gorm:create" ).Register("log" , func (db *gorm.DB) { })
理论 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
算法特点 :
依赖优先 : before 优先级高,after 优先级低
冲突检测 : 检测循环依赖并报错
特殊处理 : * 表示最前/最后
代码实现 :
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 { if c.before != "" { } 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 { 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 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 db.Callback().Create().Before("gorm:create" ).Register("validate" , func (db *gorm.DB) { if user.Name == "" { db.AddError(errors.New("name required" )) } }) 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 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 { 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 { 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: 问题驱动 问题序列 :
为什么需要回调系统?
尝试直接修改 GORM 源码添加功能
体验升级时的困难
理解回调系统的价值
回调是如何被触发的?
设置断点在 Find() 方法
追踪到 Execute()
观察回调链执行
如何保证回调顺序?
注册多个有依赖关系的回调
观察排序结果
理解 before/after 机制
模型钩子和全局回调有什么区别?
如何实现条件回调?
实现 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) { db.Callback().Create().After("gorm:create" ).Register("audit:create" , s.logCreate) db.Callback().Update().After("gorm:update" ).Register("audit:update" , s.logUpdate) 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 学习成果验收 理论验收 :
实践验收 :
综合验收 :
四、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 type callbacks struct { processors map [string ]*processor } type processor struct { db *gorm.DB Clauses []string fns []func (*gorm.DB) callbacks []*callback } type callback struct { name string 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 func (cs *callbacks) Create() *processor { return cs.processors["create" ] } func (cs *callbacks) Query() *processor { return cs.processors["query" ] } func (cs *callbacks) Update() *processor { return cs.processors["update" ] } func (cs *callbacks) Delete() *processor { return cs.processors["delete" ] } func (cs *callbacks) Row() *processor { return cs.processors["row" ] } 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 type Config struct { LastInsertIDReversed bool CreateClauses []string QueryClauses []string UpdateClauses []string DeleteClauses []string } 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" } ) func RegisterDefaultCallbacks (db *gorm.DB, config *Config) { enableTransaction := func (db *gorm.DB) bool { return !db.SkipDefaultTransaction } 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 } 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 queryCallback := db.Callback().Query() queryCallback.Register("gorm:query" , Query) queryCallback.Register("gorm:preload" , Preload) queryCallback.Register("gorm:after_query" , AfterQuery) queryCallback.Clauses = config.QueryClauses 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 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 rowCallback := db.Callback().Row() rowCallback.Register("gorm:row" , RowQuery) rowCallback.Clauses = config.QueryClauses 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 func BeforeCreate (db *gorm.DB) { if db.Error == nil && db.Statement.Schema != nil && !db.Statement.SkipHooks && (db.Statement.Schema.BeforeSave || db.Statement.Schema.BeforeCreate) { callMethod(db, func (value interface {}, tx *gorm.DB) (called bool ) { if db.Statement.Schema.BeforeSave { if i, ok := value.(BeforeSaveInterface); ok { called = true db.AddError(i.BeforeSave(tx)) } } 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 func Create (config *Config) func (db *gorm.DB) { supportReturning := utils.Contains(config.CreateClauses, "RETURNING" ) return func (db *gorm.DB) { if db.Error != nil { return } if db.Statement.Schema != nil { if !db.Statement.Unscoped { for _, c := range db.Statement.Schema.CreateClauses { db.Statement.AddClause(c) } } 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}) } } } } 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...) } if !db.DryRun && db.Error == nil { return } ok, mode := hasReturning(db, supportReturning) if ok { 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 } } 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) 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 func (p *processor) Register(name string , handler func (*gorm.DB) ) *callback { f := &callback{ name: name, handler: handler, processor: p, } p.callbacks = append (p.callbacks, f) p.fns = nil return f } func (f *callback) Before(name string ) *callback { f.before = name return f } func (f *callback) After(name string ) *callback { f.after = name return f } func (f *callback) Match(match func (*gorm.DB) bool ) *callback { f.match = match return f } func (p *processor) Replace(name string , handler func (*gorm.DB) ) *callback { for _, callback := range p.callbacks { if callback.name == name { callback.replace = true callback.handler = handler break } } p.fns = nil return nil } func (p *processor) Remove(name string ) { for _, callback := range p.callbacks { if callback.name == name { callback.remove = true break } } 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 mainimport ( "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{}) db.Callback().Create().Before("gorm:create" ).Register("my:before_create" , func (db *gorm.DB) { fmt.Println("Before create:" , db.Statement.Statement) }) db.Callback().Create().After("gorm:create" ).Register("my:after_create" , func (db *gorm.DB) { fmt.Println("After create:" , db.Statement.RowsAffected) }) db.Create(&User{Name: "John" , Email: "john@example.com" }) }
示例 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" ) 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 } 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 } 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 } 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" })
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) { if db.Statement.RowsAffected > 0 { s.db.Create(&AuditLog{ Action: "create" , TableName: db.Statement.Table, RecordID: 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 } func (u *User) BeforeDelete(tx *gorm.DB) error { 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 { 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 { return !db.DryRun }).Register("my:dryrun_check" , func (db *gorm.DB) { })
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
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{})
原因 :
SkipHooks 被设置为 true
Schema 没有检测到钩子方法
解决方案 :
1 2 3 4 5 db.Statement.SkipHooks = false db.AutoMigrate(&User{})
问题 2: 回调执行顺序错误 症状 : 自定义回调在默认回调之前执行
解决方案 :
1 2 3 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 { tx.AddError(err) return err } return nil }
七、学习验证 7.1 知识自测 基础题
GORM 支持哪些模型钩子?
A. BeforeCreate, AfterCreate
B. BeforeUpdate, AfterUpdate
C. BeforeDelete, AfterDelete
D. 以上都是
如何注册全局回调?
A. 实现 BeforeCreateInterface
B. 使用 db.Callback().Create().Register()
C. 使用 db.Callback().Create().Replace()
D. 以上都可以
回调的执行顺序由什么决定?
A. 注册顺序
B. before/after 关系
C. 回调名称字母顺序
D. 随机顺序
进阶题
如何让回调只在特定条件下执行?
使用 Match 方法
在回调函数内部判断
使用 Replace 方法
使用 Remove 方法
以下代码的执行顺序是什么?
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 { 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) { }