GORM 的钩子(Hooks)机制允许在数据库操作的不同阶段执行自定义逻辑。钩子函数会在特定操作之前或之后自动调用。
钩子类型
对象级钩子
这些钩子在对象级别触发,适用于单个对象的操作。
gotype User struct { gorm.Model Name string Email string Age int } // 创建前钩子 func (u *User) BeforeCreate(tx *gorm.DB) error { fmt.Println("BeforeCreate: 准备创建用户") if u.Name == "" { return errors.New("用户名不能为空") } return nil } // 创建后钩子 func (u *User) AfterCreate(tx *gorm.DB) error { fmt.Println("AfterCreate: 用户创建成功") return nil } // 更新前钩子 func (u *User) BeforeUpdate(tx *gorm.DB) error { fmt.Println("BeforeUpdate: 准备更新用户") return nil } // 更新后钩子 func (u *User) AfterUpdate(tx *gorm.DB) error { fmt.Println("AfterUpdate: 用户更新成功") return nil } // 保存前钩子(Create 和 Update 都会触发) func (u *User) BeforeSave(tx *gorm.DB) error { fmt.Println("BeforeSave: 准备保存用户") return nil } // 保存后钩子(Create 和 Update 都会触发) func (u *User) AfterSave(tx *gorm.DB) error { fmt.Println("AfterSave: 用户保存成功") return nil } // 删除前钩子 func (u *User) BeforeDelete(tx *gorm.DB) error { fmt.Println("BeforeDelete: 准备删除用户") return nil } // 删除后钩子 func (u *User) AfterDelete(tx *gorm.DB) error { fmt.Println("AfterDelete: 用户删除成功") return nil } // 查询后钩子 func (u *User) AfterFind(tx *gorm.DB) error { fmt.Println("AfterFind: 用户查询成功") return nil }
查询级钩子
这些钩子在查询级别触发,适用于批量操作。
go// 查询前钩子 func (u *User) BeforeQuery(tx *gorm.DB) error { fmt.Println("BeforeQuery: 准备查询用户") return nil } // 查询后钩子 func (u *User) AfterQuery(tx *gorm.DB) error { fmt.Println("AfterQuery: 用户查询成功") return nil }
钩子执行顺序
创建操作
- BeforeCreate
- BeforeSave
- 执行 INSERT
- AfterSave
- AfterCreate
更新操作
- BeforeUpdate
- BeforeSave
- 执行 UPDATE
- AfterSave
- AfterUpdate
删除操作
- BeforeDelete
- 执行 DELETE
- AfterDelete
查询操作
- BeforeQuery
- 执行 SELECT
- AfterQuery / AfterFind
实际应用场景
1. 数据验证
gofunc (u *User) BeforeCreate(tx *gorm.DB) error { if u.Age < 0 { return errors.New("年龄不能为负数") } if !strings.Contains(u.Email, "@") { return errors.New("邮箱格式不正确") } return nil }
2. 自动生成字段
gofunc (u *User) BeforeCreate(tx *gorm.DB) error { if u.ID == 0 { u.ID = generateUUID() } return nil }
3. 数据加密
gofunc (u *User) BeforeSave(tx *gorm.DB) error { if u.Password != "" { u.Password = hashPassword(u.Password) } return nil }
4. 时间戳管理
gofunc (u *User) BeforeCreate(tx *gorm.DB) error { now := time.Now() u.CreatedAt = now u.UpdatedAt = now return nil } func (u *User) BeforeUpdate(tx *gorm.DB) error { u.UpdatedAt = time.Now() return nil }
5. 审计日志
gofunc (u *User) AfterCreate(tx *gorm.DB) error { log.Printf("用户创建: ID=%d, Name=%s", u.ID, u.Name) return nil } func (u *User) AfterUpdate(tx *gorm.DB) error { log.Printf("用户更新: ID=%d, Name=%s", u.ID, u.Name) return nil } func (u *User) AfterDelete(tx *gorm.DB) error { log.Printf("用户删除: ID=%d", u.ID) return nil }
6. 软删除处理
gofunc (u *User) BeforeDelete(tx *gorm.DB) error { // 软删除时更新删除时间 if tx.Statement.Unscoped { // 真正删除 return nil } // 软删除,更新 DeletedAt return nil }
钩子中的事务操作
在钩子中可以访问事务上下文:
gofunc (u *User) AfterCreate(tx *gorm.DB) error { // 在同一个事务中创建关联记录 profile := Profile{ UserID: u.ID, Bio: "新用户", } return tx.Create(&profile).Error }
跳过钩子
有时需要跳过钩子执行:
go// 跳过所有钩子 db.Session(&gorm.Session{SkipHooks: true}).Create(&user) // 使用 Unscoped 跳过软删除钩子 db.Unscoped().Delete(&user)
钩子返回错误
钩子返回错误会阻止操作继续执行:
gofunc (u *User) BeforeCreate(tx *gorm.DB) error { if u.Name == "admin" { return errors.New("不允许创建 admin 用户") } return nil } // 使用 err := db.Create(&user).Error if err != nil { fmt.Println("创建失败:", err) }
注意事项
- 性能影响:钩子会增加操作开销,避免在钩子中执行耗时操作
- 事务一致性:钩子中的操作与主操作在同一事务中,注意错误处理
- 避免循环:钩子中不要触发会导致无限循环的操作
- 错误处理:钩子返回错误会阻止操作,确保正确处理错误
- 批量操作:批量操作时,钩子会对每条记录执行,注意性能
- 测试覆盖:钩子逻辑需要充分的单元测试覆盖
最佳实践
- 保持简单:钩子逻辑应该简单明了,避免复杂业务逻辑
- 单一职责:每个钩子只做一件事
- 日志记录:在钩子中添加适当的日志记录
- 错误信息:提供清晰的错误信息,便于调试
- 文档说明:为复杂的钩子逻辑添加注释说明