红魔咖啡馆

头发越掉越多,头发越掉越少

0%

【GORM】事务

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():提交事务

保存点

可以通过 SavePointRollbackto 方法创建保存点和回滚至保存点

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