匿名函数与gorm中的Transaction事务方法

发布于:2024-04-18 ⋅ 阅读:(28) ⋅ 点赞:(0)

整理下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))
	}
}()