【GORM】事务

  • ~3.65K 字
  1. 1. Hook
    1. 1.1. 创建操作
    2. 1.2. 更新操作
    3. 1.3. 删除操作
    4. 1.4. 查询操作
    5. 1.5. 修改当前操作
    6. 1.6. 事务中查询其他表
  2. 2. 事务
    1. 2.1. 默认事务
    2. 2.2. 语法
    3. 2.3. 嵌套事务
    4. 2.4. 手动事务
    5. 2.5. 保存点

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
赞助喵
非常感谢您的喜欢!
赞助喵