// 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")