关于 golang 的 Gorm 中钩子函数的示例

https://gorm.io/zh_CN/docs/hooks.html


// Hook 是在创建、查询、更新、删除等操作之前、之后调用的函数
// 若已经为模型定义了上述相关方法,则会在创建、更新、查询、删除时被自动调用
// 若任何回调返回错误,则停止后续操作并回滚事务
// 钩子方法的函数签名 func(*gorm.DB) error

// 可以通过 "SkipHooks" 会话模式来跳过钩子方法,例如:
DB.Session(&gorm.Session{SkipHooks: true}).Create(&user)
DB.Session(&gorm.Session{SkipHooks: true}).Create(&users)
DB.Session(&gorm.Session{SkipHooks: true}).CreateInBatches(users, 100)

创建对象


// 在创建时允许用户定义的钩子有 BeforeSave, BeforeCreate, AfterSave, AfterCreate

// 开始事务
BeforeSave
BeforeCreate
// 关联前的 save
// 插记录至 database
// 关联后的 save
AfterCreate
AfterSave
// 提交或回滚事务

// ~~~~~~~~~~~~~~~~~~~~~~~~~~ Example

func (u *User) BeforeCreate(tx *gorm.DB) (err error) {
  u.UUID = uuid.New()

  if !u.IsValid() {
    err = errors.New("can't save invalid data")
  }
  if u.Role == "admin" {
      return errors.New("invalid role")
  }
  return
}

func (u *User) AfterCreate(tx *gorm.DB) (err error) {
  if u.ID == 1 {
    tx.Model(u).Update("role", "admin")
  }
  return
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~ Example

// 若钩子返回任何错误,则修改将被回滚(保存、删除的操作会默认运行在事务上,因此在事务完成前该事务中所作的更改是不可见的)
func (u *User) AfterCreate(tx *gorm.DB) (err error) {
  if !u.IsValid() {
    return errors.New("rollback invalid user")
  }
  return nil
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~ Example

// 修改当前操作
func (u *User) BeforeCreate(tx *gorm.DB) error {
  // 通过 tx.Statement 修改当前操作:
  tx.Statement.Select("Name", "Age")
  tx.Statement.AddClause(clause.OnConflict{DoNothing: true})

  // tx 是带有 `NewDB` 选项的新会话模式 
  // 基于 tx 的操作会在同一个事务中,但不会带上任何当前的条件
  err := tx.First(&role, "name = ?", user.Role).Error
  // SELECT * FROM roles WHERE name = "admin"
  // ...
  return err
}

更新对象


// 当更新时允许用户定义的钩子有 BeforeSave、BeforeUpdate、AfterSave、AfterUpdate

// 开始事务
BeforeSave
BeforeUpdate
// 关联前的 save
// 更新 db
// 关联后的 save
AfterUpdate
AfterSave
// 提交或回滚事务

// ~~~~~~~~~~~~~~~~~~~~~~~~~~ Example

func (u *User) BeforeUpdate(tx *gorm.DB) (err error) {
  if u.readonly() {
    err = errors.New("read only user")
  }
  if u.Role == "admin" {
      return errors.New("admin user not allowed to update")
  }
  return
}

// 在同一个事务中更新数据
func (u *User) AfterUpdate(tx *gorm.DB) (err error) {
  if u.Confirmed {
    tx.Model(&Address{}).Where("user_id = ?", u.ID).Update("verfied", true)
  }
  return
}

删除对象

// 当删除时允许用户定义的钩子有 BeforeDelete、AfterDelete

// 开始事务
BeforeDelete
// 删除 db 中的数据
AfterDelete
// 提交或回滚事务

// ~~~~~~~~~~~~~~~~~~~~~~~~~~ Example

func (u *User) BeforeDelete(tx *gorm.DB) (err error) {
    if u.Role == "admin" {
        return errors.New("admin user not allowed to delete")
    }
    return
}

// 在同一个事务中更新数据
func (u *User) AfterDelete(tx *gorm.DB) (err error) {
  if u.Confirmed {
    tx.Model(&Address{}).Where("user_id = ?", u.ID).Update("invalid", false)
  }
  return
}

查询对象

// 查询时允许用户定义的钩子有 AfterFind (查询记录后会调用它)

// 从 database 中加载数据
// Preloading (eager loading)
AfterFind

// ~~~~~~~~~~~~~~~~~~~~~~~~~~ Example

func (u *User) AfterFind(tx *gorm.DB) (err error) {
  if u.Role == "" {
    u.Role = "user"
  }
  return
}

Update Tips ...


// 若想在更新时跳过钩子方法且不追踪更新时间,可使用 UpdateColumn、UpdateColumns 方法

// 更新单个列
db.Model(&user).UpdateColumn("name", "hello")
// UPDATE users SET name='hello' WHERE id = 111;

// 更新多个列
db.Model(&user).UpdateColumns(User{Name: "hello", Age: 18})
// UPDATE users SET name='hello', age=18 WHERE id = 111;

// 更新特定列
db.Model(&user).Select("name", "age").UpdateColumns(User{Name: "hello", Age: 0})
// UPDATE users SET name='hello', age=0 WHERE id = 111;

// -------------------------------------------------------------------------- 检查字段是否被改变
// Changed 方法可以被用在 BeforeUpdate 钩子中,它返回字段是否有变更的布尔值
// Changed 方法只能与 Update、Updates 方法一起使用
// 并且它只检查 Model 对象字段的值与 Update、Updates 的值是否相等,若值有变更且字段没有被忽略,则返回 true

func (u *User) BeforeUpdate(tx *gorm.DB) (err error) {
    // 若 Role 字段有变更
    if tx.Statement.Changed("Role") {
      return errors.New("role not allowed to change")
    }

    // 若 Name 或 Role 字段有变更
    if tx.Statement.Changed("Name", "Admin") { 
      tx.Statement.SetColumn("Age", 18)
    }

    // 若任意字段有变更
    if tx.Statement.Changed() {
      tx.Statement.SetColumn("RefreshedAt", time.Now())
    }
    return nil
}

db.Model(&User{ID: 1, Name: "jinzhu"}).Updates(map[string]interface{"name": "jinzhu2"})
// Changed("Name") => true

db.Model(&User{ID: 1, Name: "jinzhu"}).Updates(map[string]interface{"name": "jinzhu"})
// Changed("Name") => false, 因为 `Name` 没有变更

db.Model(&User{ID: 1, Name: "jinzhu"}).Select("Admin").Updates(map[string]interface{
  "name": "jinzhu2", "admin": false,
})
// Changed("Name") => false, 因为 `Name` 没有被 Select 选中并更新

db.Model(&User{ID: 1, Name: "jinzhu"}).Updates(User{Name: "jinzhu2"})
// Changed("Name") => true

db.Model(&User{ID: 1, Name: "jinzhu"}).Updates(User{Name: "jinzhu"})
// Changed("Name") => false, 因为 `Name` 未变更

db.Model(&User{ID: 1, Name: "jinzhu"}).Select("Admin").Updates(User{Name: "jinzhu2"})
// Changed("Name") => false, 因为 `Name` 没有被 Select 选中并更新

// -------------------------------------------------------------------------- 在 Update 时修改值
// 若要在 Before 钩子中改变要更新的值
// 如果它是一个完整的更新,可以使用 Save,否则,应使用 SetColumn,例如:

func (user *User) BeforeSave(tx *gorm.DB) (err error) {
  if pw, err := bcrypt.GenerateFromPassword(user.Password, 0); err == nil {
    tx.Statement.SetColumn("EncryptedPassword", pw)
  }

  if tx.Statement.Changed("Code") {
    user.Age += 20
    tx.Statement.SetColumn("Age", user.Age)
  }
}

db.Model(&user).Update("Name", "jinzhu")

// -------------------------------------------------------------------------- 在 Update 时修改值
// 可以在 BeforeSave 钩子中改变要更新的值
// 如果它是一个完整的更新,则可以使用 Save 方法,否则应使用 SetColumn 方法,例如:

func (user *User) BeforeSave(tx *gorm.DB) (err error) {
  if pw, err := bcrypt.GenerateFromPassword(user.Password, 0); err == nil {
    tx.Statement.SetColumn("EncryptedPassword", pw)
  }

  if tx.Statement.Changed("Code") {
    user.Age += 20
    tx.Statement.SetColumn("Age", user.Age)
  }
}

db.Model(&user).Update("Name", "jinzhu")

posted @   2gbxzhdaz  阅读(1209)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
点击右上角即可分享
微信分享提示