事务和Hook
1. 会话Session
为了避免共用db导致的一些问题,gorm提供了会话模式,通过新建session的形式,将db的操作分离,互不影响。
创建session的时候,有一些配置:
// Session 配置
type Session struct {DryRun bool //生成 SQL 但不执行PrepareStmt bool //预编译模式NewDB bool //新db 不带之前的条件Initialized bool //初始化新的dbSkipHooks bool //跳过钩子SkipDefaultTransaction bool //禁用默认事务DisableNestedTransaction bool //禁用嵌套事务AllowGlobalUpdate bool //允许不带条件的更新FullSaveAssociations bool //允许更新关联数据QueryFields bool //select(字段)Context context.ContextLogger logger.InterfaceNowFunc func() time.Time //允许改变 GORM 获取当前时间的实现CreateBatchSize int
}
比如说可以禁用默认的事务,从而提供性能,官方说大致能提升30%左右:
// 持续会话模式
tx := db.Session(&Session{SkipDefaultTransaction: true})
tx.First(&user, 1)
tx.Find(&users)
tx.Model(&user).Update("Age", 18)
比如使用PreparedStmt
在执行任何 SQL 时都会创建一个 prepared statement 并将其缓存,以提高后续的效率
// 会话模式
tx := db.Session(&Session{PrepareStmt: true})
tx.First(&user, 1)
tx.Find(&users)
tx.Model(&user).Update("Age", 18)// returns prepared statements manager
stmtManger, ok := tx.ConnPool.(*PreparedStmtDB)// 关闭 *当前会话* 的预编译模式
stmtManger.Close()// 为 *当前会话* 预编译 SQL
stmtManger.PreparedSQL // => []string{}// 为当前数据库连接池的(所有会话)开启预编译模式
stmtManger.Stmts // map[string]*sql.Stmtfor sql, stmt := range stmtManger.Stmts {sql // 预编译 SQLstmt // 预编译模式stmt.Close() // 关闭预编译模式
}
还有,gorm的db默认是协程安全的,如果使用初始化参数,则db不在协程安全:
tx := db.Session(&gorm.Session{Initialized: true})
比如context:
timeoutCtx, _ := context.WithTimeout(context.Background(), time.Second)
tx := db.Session(&Session{Context: timeoutCtx})tx.First(&user) // 带有 context timeoutCtx 的查询操作
tx.Model(&user).Update("role", "admin") // 带有 context timeoutCtx 的更新操作
2. 事务
2.1 自动事务
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
})
2.2 嵌套事务
GORM 支持嵌套事务,您可以回滚较大事务内执行的一部分操作,例如:
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(tx2 *gorm.DB) error {tx2.Create(&user3)return nil})return nil
})// Commit user1, user3
2.3 手动事务
// 开始事务
tx := db.Begin()// 在事务中执行一些 db 操作(从这里开始,您应该使用 'tx' 而不是 'db')
tx.Create(...)// ...// 遇到错误时回滚事务
tx.Rollback()// 否则,提交事务
tx.Commit()
比如
// 开启事务
tx := db.Begin()//在事务中执行数据库操作,使用的是tx变量,不是db。
//库存减一
//等价于: UPDATE `goods` SET `stock` = stock - 1 WHERE `goods`.`id` = '2' and stock > 0
//RowsAffected用于返回sql执行后影响的行数
rowsAffected := tx.Model(&goods).Where("stock > 0").Update("stock", gorm.Expr("stock - 1")).RowsAffected
if rowsAffected == 0 {//如果更新库存操作,返回影响行数为0,说明没有库存了,结束下单流程//这里回滚作用不大,因为前面没成功执行什么数据库更新操作,也没什么数据需要回滚。//这里就是举个例子,事务中可以执行多个sql语句,错误了可以回滚事务tx.Rollback()return
}
err := tx.Create(保存订单).Error//保存订单失败,则回滚事务
if err != nil {tx.Rollback()
} else {tx.Commit()
}
2.4 保存点
GORM 提供了 SavePoint
、Rollbackto
方法,来提供保存点以及回滚至保存点功能,例如:
tx := db.Begin()
tx.Create(&user1)tx.SavePoint("sp1")
tx.Create(&user2)
tx.RollbackTo("sp1") // Rollback user2tx.Commit() // Commit user1
3. Hook
Hook 是在创建、查询、更新、删除等操作之前、之后调用的函数。
如果您已经为模型定义了指定的方法,它会在创建、更新、查询、删除时自动被调用。如果任何回调返回错误,GORM 将停止后续的操作并回滚事务。
钩子方法的函数签名应该是 func(*gorm.DB) error
3.1 创建
创建时可用的 hook
// 开始事务
BeforeSave
BeforeCreate
// 关联前的 save
// 插入记录至 db
// 关联后的 save
AfterCreate
AfterSave
// 提交或回滚事务
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 中保存、删除操作会默认运行在事务上, 因此在事务完成之前该事务中所作的更改是不可见的,如果您的钩子返回了任何错误,则修改将被回滚。
func (u *User) AfterCreate(tx *gorm.DB) (err error) {if !u.IsValid() {return errors.New("rollback invalid user")}return nil
}
3.2 更新
更新时可用的 hook
// 开始事务
BeforeSave
BeforeUpdate
// 关联前的 save
// 更新 db
// 关联后的 save
AfterUpdate
AfterSave
// 提交或回滚事务
代码示例:
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
}
3.3 删除
删除时可用的 hook
// 开始事务
BeforeDelete
// 删除 db 中的数据
AfterDelete
// 提交或回滚事务
代码示例:
// 在同一个事务中更新数据
func (u *User) AfterDelete(tx *gorm.DB) (err error) {if u.Confirmed {tx.Model(&Address{}).Where("user_id = ?", u.ID).Update("invalid", false)}return
}
3.4 查询
查询时可用的 hook
// 从 db 中加载数据
// Preloading (eager loading)
AfterFind
代码示例:
func (u *User) AfterFind(tx *gorm.DB) (err error) {if u.MemberShip == "" {u.MemberShip = "user"}return
}