乐闻世界logo
搜索文章和话题

GORM 中的钩子(Hooks)是如何工作的?

3月7日 19:44

GORM 的钩子(Hooks)机制允许在数据库操作的不同阶段执行自定义逻辑。钩子函数会在特定操作之前或之后自动调用。

钩子类型

对象级钩子

这些钩子在对象级别触发,适用于单个对象的操作。

go
type 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 }

钩子执行顺序

创建操作

  1. BeforeCreate
  2. BeforeSave
  3. 执行 INSERT
  4. AfterSave
  5. AfterCreate

更新操作

  1. BeforeUpdate
  2. BeforeSave
  3. 执行 UPDATE
  4. AfterSave
  5. AfterUpdate

删除操作

  1. BeforeDelete
  2. 执行 DELETE
  3. AfterDelete

查询操作

  1. BeforeQuery
  2. 执行 SELECT
  3. AfterQuery / AfterFind

实际应用场景

1. 数据验证

go
func (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. 自动生成字段

go
func (u *User) BeforeCreate(tx *gorm.DB) error { if u.ID == 0 { u.ID = generateUUID() } return nil }

3. 数据加密

go
func (u *User) BeforeSave(tx *gorm.DB) error { if u.Password != "" { u.Password = hashPassword(u.Password) } return nil }

4. 时间戳管理

go
func (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. 审计日志

go
func (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. 软删除处理

go
func (u *User) BeforeDelete(tx *gorm.DB) error { // 软删除时更新删除时间 if tx.Statement.Unscoped { // 真正删除 return nil } // 软删除,更新 DeletedAt return nil }

钩子中的事务操作

在钩子中可以访问事务上下文:

go
func (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)

钩子返回错误

钩子返回错误会阻止操作继续执行:

go
func (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) }

注意事项

  1. 性能影响:钩子会增加操作开销,避免在钩子中执行耗时操作
  2. 事务一致性:钩子中的操作与主操作在同一事务中,注意错误处理
  3. 避免循环:钩子中不要触发会导致无限循环的操作
  4. 错误处理:钩子返回错误会阻止操作,确保正确处理错误
  5. 批量操作:批量操作时,钩子会对每条记录执行,注意性能
  6. 测试覆盖:钩子逻辑需要充分的单元测试覆盖

最佳实践

  1. 保持简单:钩子逻辑应该简单明了,避免复杂业务逻辑
  2. 单一职责:每个钩子只做一件事
  3. 日志记录:在钩子中添加适当的日志记录
  4. 错误信息:提供清晰的错误信息,便于调试
  5. 文档说明:为复杂的钩子逻辑添加注释说明
标签:Gorm