gorm事务

准备工作

建立数据库连接

import (
	"fmt"
	_ "github.com/go-sql-driver/mysql"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"log"
)

var db *gorm.DB

func OpenDB() {
	dsn := "root:adss123@tcp(127.0.0.1:3306)/go_db?charset=utf8mb4&parseTime=True&loc=Local"
	res, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
	db = res
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("成功:%v\n", db)
}

建立一个表

type TransactionTest struct {
	gorm.Model
	Name string
}

禁用默认事务

为了确保数据一致性,GORM 会在事务里执行写入操作(创建、更新、删除)。如果没有这方面的要求,您可以在初始化时禁用它,这将获得大约 30%+ 性能提升。

// 全局禁用
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)

事务

要在事务中执行一系列操作,一般流程如下:

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
})

实验案例如下
正常创建一条记录

func InsertTra() {
	OpenDB()
	t := &TransactionTest{Name: "test1"}
	db.Transaction(func(tx *gorm.DB) error {
		tx.Create(t)
		return nil
	})
}

在这里插入图片描述
当Transaction返回值为error时,事务会作废。但是Transaction函数内命令都会执行一遍

func InsertTra() {
	OpenDB()
	t := &TransactionTest{Name: "test1"}
	t2 := &TransactionTest{Name: "test2"}
	db.Transaction(func(tx *gorm.DB) error {
		tx.Create(t)
		tx.Create(t2)
		fmt.Println("函数执行过")
		return errors.New("失败测试...")
	})
}

在这里插入图片描述
事务没执行
在这里插入图片描述
但是函数命令都过了一遍。

嵌套事务

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

实验案例如下

func InsertTra() {
	OpenDB()
	t := &TransactionTest{Name: "test1"}
	t2 := &TransactionTest{Name: "test2"}
	t3 := &TransactionTest{Name: "test3"}
	db.Transaction(func(tx *gorm.DB) error {
		tx.Create(t)
		tx.Create(t2)
		tx.Transaction(func(tx *gorm.DB) error {
			tx.Create(t3)
			return errors.New("失败测试")
		})
		return nil
	})
}

函数目的是,在创建t与t2记录的事务下嵌套一个创建t3的事务。
执行结果为:
在这里插入图片描述
发现t3没被创建,发现被嵌套的事务是否执行,不影响外层事务的执行。而且发现事务的回滚是会影响自增长的字段的。被回滚的记录还是会导致自增长字段改变。

手动事务

Gorm 支持直接调用事务控制方法(commit、rollback),例如:

// 开始事务
tx := db.Begin()

// 在事务中执行一些 db 操作(从这里开始,您应该使用 'tx' 而不是 'db')
tx.Create(...)

// ...

// 遇到错误时回滚事务
tx.Rollback()

// 否则,提交事务
tx.Commit()

实验案例如下
现在以flag的正假来模拟error

func InsertTra() {
	OpenDB()
	flag := true
	t := &TransactionTest{Name: "test1"}
	t2 := &TransactionTest{Name: "test2"}
	t3 := &TransactionTest{Name: "test3"}
	tx := db.Begin()
	tx.Create(t)
	tx.Create(t2)
	tx.Create(t3)
	if flag {
		tx.Rollback()
	} else {
		tx.Commit()
	}

}

当flag为真的时候,事务回滚,flag为假的时候事务执行。
现在测试tx的生命周期

func InsertTra() {
	OpenDB()
	flag := true
	t := &TransactionTest{Name: "test1"}
	t2 := &TransactionTest{Name: "test2"}
	t3 := &TransactionTest{Name: "test3"}
	tx := db.Begin()
	tx.Create(t)
	tx.Create(t2)

	if flag {
		tx.Rollback()
	} else {
		tx.Commit()
	}
	tx.Create(t3)
}

在这里插入图片描述
发现tx的生命周期从begin()开始,到Rollback(),Commit()结束。

SavePoint、RollbackTo

GORM 提供了 SavePoint、Rollbackto 方法,来提供保存点以及回滚至保存点功能,例如:

tx := db.Begin()
tx.Create(&user1)

tx.SavePoint("sp1")
tx.Create(&user2)
tx.RollbackTo("sp1") // Rollback user2

tx.Commit() // Commit user1

实验代码如下

func InsertTra() {
	OpenDB()
	t := &TransactionTest{Name: "test1"}
	t2 := &TransactionTest{Name: "test2"}
	t3 := &TransactionTest{Name: "test3"}
	tx := db.Begin()
	tx.Create(t)
	tx.Create(t2)
	tx.SavePoint("sp1")
	tx.Create(t3)
	tx.RollbackTo("sp1")
	tx.Commit()
}

在这里插入图片描述
发现只创建了t和t2,说明 SavePoint、RollbackTo操作成功,也就是间接实现了上文的嵌套事务的功能。