整理下go中的匿名函数,项目中很多地方都在用。
1、函数类型的变量
Go中,函数也是一种数据类型。定义一个函数,把这个函数赋值给一个变量,这个变量就是函数类型的变量,用这个变量等价于直接调函数
:
package main
import "fmt"
//定义一个函数:
func test(num int){
fmt.Println(num)
}
func main(){
//函数也是一种数据类型,可以赋值给一个变量
a := test //a变量就是一个函数类型的变量
fmt.Printf("a的类型是:%T,test函数的类型是:%T \n",a,test) //a的类型是:func(int),test函数的类型是:func(int)
//通过该变量可以对函数调用
a(10) //等价于 test(10)
}
2、函数类型的形参
有函数类型的变量,那函数类型的形参也可以。对于函数类型的形参,可以选择传入类型相同的函数名称或者变量,也可以用匿名函数直接写逻辑
。
package main
import "fmt"
//定义一个函数
func test(num int){
fmt.Println(num)
}
//定义一个函数,把另一个函数作为形参
func test02 (num1 int ,num2 float32, fc func(int)){
fc(num1) //调用形参对应的函数
}
func main(){
//a变量就是一个函数类型的变量
a := test
//传入类型相同的函数名称
test02(1, 3.14, test)
//传入类型相同的函数类型的变量
test02(2, 3.14, a)
//传入一个匿名函数
test02(3, 3.14, func(num int) {
fmt.Println(num)
})
}
3、gorm中的Transaction方法
gorm框架中的Transaction方法,形参是一个函数类型func(tx *gorm.DB) error
,下面直接用匿名函数给Transaction方法传参,匿名函数中自然是拿gorm.DB对象去做一系列的insert操作
// 使用事务执行数据库操作
err := db.Transaction(func(tx *gorm.DB) error {
// 在事务中执行一系列数据库操作
if err := tx.Model(&User{}).Create(&User{Name: "John"}).Error; err != nil {
// 如果其中一个操作失败,则回滚事务
return err
}
if err := tx.Model(&Order{}).Create(&Order{UserID: 1, Product: "Phone"}).Error; err != nil {
// 如果其中一个操作失败,则回滚事务
return err
}
// 如果所有操作都成功,则提交事务
return nil
})
if err != nil {
// 处理事务执行过程中的错误
log.Println("Transaction failed:", err)
}
db.Transaction 方法开启了一个数据库事务,在事务中,依次执行了两个数据库操作:创建一个新用户和创建一个新订单。如果其中一个操作失败,则事务会回滚,所有之前的操作都会被撤销。如果所有操作都成功,则事务会被提交。
以上写法基本固定,有一步Create失败,然会error,事务回滚,执行到最后,说明前面的一个个Create都成功,return nil
,提交事务。
4、Transaction方法源码
// Transaction start a transaction as a block, return error will rollback, otherwise to commit. Transaction executes an
// arbitrary number of commands in fc within a transaction. On success the changes are committed; if an error occurs
// they are rolled back.
func (db *DB) Transaction(fc func(tx *DB) error, opts ...*sql.TxOptions) (err error) {
//声明一个变量 panicked,用于标记是否发生了 panic
panicked := true
//检查当前数据库连接池是否实现了 TxCommitter 接口,并且该接口不为 nil。
//如果满足条件,说明支持嵌套事务,可以使用保存点进行处理
if committer, ok := db.Statement.ConnPool.(TxCommitter); ok && committer != nil {
// nested transaction(嵌套事务)
//检查是否禁用了嵌套事务。如果未禁用,将创建保存点来实现嵌套事务
if !db.DisableNestedTransaction {
//创建一个保存点,命名为函数指针的字符串表示形式,并将错误赋给变量 err
err = db.SavePoint(fmt.Sprintf("sp%p", fc)).Error
if err != nil {
return
}
defer func() {
// 善后处理,如果发生了 panic 或者函数执行过程中出现了错误,将回滚到之前创建的保存点
if panicked || err != nil {
db.RollbackTo(fmt.Sprintf("sp%p", fc))
}
}()
}
//调用传入的函数 fc,并将一个新的 Session 对象作为参数传递给它
//Session 对象会创建一个新的数据库连接,如果 db.clone 的值为 1,则表示创建一个新的数据库连接
//数据库连接对象传入给函数类型的变量fc
//我上面写的匿名函数就被调用
//开始执行我的一些列Create方法
//执行完成后,把错误赋给err变量
//err不为空,自会被defer中的匿名函数回滚
err = fc(db.Session(&Session{NewDB: db.clone == 1}))
//当前数据库连接池不支持嵌套事务,或者嵌套事务被禁用
} else {
//开始一个新的数据库事务,并将返回的事务赋给变量 tx
tx := db.Begin(opts...)
if tx.Error != nil {
return tx.Error
}
defer func() {
// 同样的defer搭配匿名函数,有一步err就回滚
if panicked || err != nil {
tx.Rollback()
}
}()
//函数类型的变量fx,调用我传入的匿名函数,进行一系列Create
if err = fc(tx); err == nil {
//没发生错误,改标志位为false
panicked = false
//事务提交
return tx.Commit().Error
}
}
panicked = false
return
}
上面源码中也体现了匿名函数的另一个用途:搭配defer
defer func() {
// Make sure to rollback when panic, Block error or Commit error
if panicked || err != nil {
db.RollbackTo(fmt.Sprintf("sp%p", fc))
}
}()