准备工作
建立数据库连接
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操作成功,也就是间接实现了上文的嵌套事务的功能。