Hook
Hook是在CRUD操作之前之后调用的函数,使用时只需要在对应模型上定义hook方法即可,常见的hook方法:
| 操作 | 钩子方法 |
|---|---|
| 创建前/后 | BeforeCreate, AfterCreate |
| 更新前/后 | BeforeUpdate, AfterUpdate |
| 删除前/后 | BeforeDelete, AfterDelete |
| 查询后 | AfterFind |
| 保存前/后(含创建和更新) | BeforeSave, AfterSave |
创建操作
1
2
3
4
5
6
7
8
9
// 开始事务
BeforeSave
BeforeCreate
// 关联前的 save
// 插入记录至 db
// 关联后的 save
AfterCreate
AfterSave
// 提交或回滚事务示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 自动生成uuid+数据校验,检查字段合法性
func (u *User) BeforeCreate(tx *gorm.DB) (err error) {
u.UUID = uuid.New()
if !u.IsValid() {
err = errors.New("can't save invalid data")
}
return
}
// 把第一个用户设置为管理员
func (u *User) AfterCreate(tx *gorm.DB) (err error) {
if u.ID == 1 {
tx.Model(u).Update("role", "admin")
}
return
}注意:GORM中保存和删除操作默认运行在事务上,在事务完成之前事务中所作更改是不可见的,若hook发生错误,修改将回滚
更新操作
1
2
3
4
5
6
7
8
9
// 开始事务
BeforeSave
BeforeUpdate
// 关联前的 save
// 更新 db
// 关联后的 save
AfterUpdate
AfterSave
// 提交或回滚事务示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 阻止某些readonly的数据被更新
func (u *User) BeforeUpdate(tx *gorm.DB) (err error) {
if u.readonly() {
err = errors.New("read only user")
}
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
}删除操作
1
2
3
4
5
// 开始事务
BeforeDelete
// 删除 db 中的数据
AfterDelete
// 提交或回滚事务示例:
1
2
3
4
5
6
7
// 在同一个事务中更新数据,保证原子性
func (u *User) AfterDelete(tx *gorm.DB) (err error) {
if u.Confirmed {
tx.Model(&Address{}).Where("user_id = ?", u.ID).Update("invalid", false)
}
return
}查询操作
1
2
3
// 从 db 中加载数据
// Preloading (eager loading)
AfterFind示例:
1
2
3
4
5
6
func (u *User) AfterFind(tx *gorm.DB) (err error) {
if u.MemberShip == "" {
u.MemberShip = "user"
}
return
}修改当前操作
通过tx.Statement修改接下来执行的操作
1
2
3
4
5
func (u *User) BeforeCreate(tx *gorm.DB) error {
// 通过 tx.Statement 修改当前操作,例如:
tx.Statement.Select("Name", "Age")
tx.Statement.AddClause(clause.OnConflict{DoNothing: true})
}这里限定了接下来的操作只对name和age两个字段起作用,且若发生冲突则跳过
事务中查询其他表
1
2
3
4
5
6
7
8
func (u *User) BeforeCreate(tx *gorm.DB) error {
// tx 是带有 `NewDB` 选项的新会话模式
// 基于 tx 的操作会在同一个事务中,但不会带上任何当前的条件
err := tx.First(&role, "name = ?", user.Role).Error
// SELECT * FROM roles WHERE name = "admin"
// ...
return err
}tx是hook传入的会话,和即将执行的insert共享一个事务,tx.First会在该事务中执行SELECT查询roles表,获取角色信息
可以基于查询结果继续执行逻辑
事务
默认事务
GORM会在事务里执行写入操作(创建更新删除),若没有要求,可以在初始化时禁用它,可以获得30%+性能提升
1
2
3
4
5
6
7
8
9
10
// 全局禁用
db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{
SkipDefaultTransaction: true,
})
// 持续会话模式
tx := db.Session(&Session{SkipDefaultTransaction: true})
tx.First(&user, 1)
tx.Find(&users)
tx.Model(&user).Update("Age", 18)语法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
db.Transaction(func(tx *gorm.DB) error {
// 在事务中执行一些 db 操作(从这里开始,您应该使用 'tx' 而不是 'db')
if err := tx.Create(&Animal{Name: "Giraffe"}).Error; err != nil {
// 返回任何错误都会回滚事务
return err
}
if err := tx.Create(&Animal{Name: "Lion"}).Error; err != nil {
return err
}
// 返回 nil 提交事务
return nil
})嵌套事务
支持嵌套事务,可以回滚大事务中的一部分操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
db.Transaction(func(tx *gorm.DB) error {
tx.Create(&user1)
tx.Transaction(func(tx2 *gorm.DB) error {
tx2.Create(&user2)
return errors.New("rollback user2") // Rollback user2
})
tx.Transaction(func(tx3 *gorm.DB) error {
tx3.Create(&user3)
return nil
})
return nil
})
// Commit user1, user3手动事务
可以手动控制事务开启关闭的方法
db.Begin():开始事务,这之后调用的函数需要以事务名调用tx.Create():创建db操作tx.Rollback():遇到错误时回滚错误tx.Commit():提交事务
保存点
可以通过 SavePoint、Rollbackto
方法创建保存点和回滚至保存点
1
2
3
4
5
6
7
8
tx := db.Begin()
tx.Create(&user1)
tx.SavePoint("sp1")
tx.Create(&user2)
tx.RollbackTo("sp1") // Rollback user2
tx.Commit() // Commit user1