关联和事务操作

main
han-joker 1 year ago
parent b0a545a6d8
commit 32d78269f8

@ -0,0 +1,397 @@
package gormExample
import (
"fmt"
"gorm.io/gorm/clause"
"log"
)
func StdAssocModel() {
// 利用migrate创建表
// 以及多对多的关联表
// 以及外键约束
if err := DB.AutoMigrate(&Author{}, &Essay{}, &Tag{}, &EssayMate{}); err != nil {
log.Fatalln(err)
}
// CREATE TABLE `msb_author` (
// `id` bigint unsigned NOT NULL AUTO_INCREMENT,
// `created_at` datetime(3) DEFAULT NULL,
// `updated_at` datetime(3) DEFAULT NULL,
// `deleted_at` datetime(3) DEFAULT NULL,
// `status` bigint DEFAULT NULL,
// `name` longtext,
// `email` longtext,
// PRIMARY KEY (`id`),
// KEY `idx_msb_author_deleted_at` (`deleted_at`)
//) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
// CREATE TABLE `msb_essay` (
// `id` bigint unsigned NOT NULL AUTO_INCREMENT,
// `created_at` datetime(3) DEFAULT NULL,
// `updated_at` datetime(3) DEFAULT NULL,
// `deleted_at` datetime(3) DEFAULT NULL,
// `subject` longtext,
// `content` longtext,
// `author_id` bigint unsigned DEFAULT NULL,
// PRIMARY KEY (`id`),
// KEY `idx_msb_essay_deleted_at` (`deleted_at`),
// KEY `fk_msb_author_essays` (`author_id`),
// CONSTRAINT `fk_msb_author_essays` FOREIGN KEY (`author_id`) REFERENCES `msb_author` (`id`)
//) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
// CREATE TABLE `msb_essay_mate` (
// `id` bigint unsigned NOT NULL AUTO_INCREMENT,
// `created_at` datetime(3) DEFAULT NULL,
// `updated_at` datetime(3) DEFAULT NULL,
// `deleted_at` datetime(3) DEFAULT NULL,
// `keyword` longtext,
// `description` longtext,
// `essay_id` bigint unsigned DEFAULT NULL,
// PRIMARY KEY (`id`),
// KEY `idx_msb_essay_mate_deleted_at` (`deleted_at`),
// KEY `fk_msb_essay_essay_mate` (`essay_id`),
// CONSTRAINT `fk_msb_essay_essay_mate` FOREIGN KEY (`essay_id`) REFERENCES `msb_essay` (`id`)
//) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
// CREATE TABLE `msb_tag` (
// `id` bigint unsigned NOT NULL AUTO_INCREMENT,
// `created_at` datetime(3) DEFAULT NULL,
// `updated_at` datetime(3) DEFAULT NULL,
// `deleted_at` datetime(3) DEFAULT NULL,
// `title` longtext,
// PRIMARY KEY (`id`),
// KEY `idx_msb_tag_deleted_at` (`deleted_at`)
//) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
// CREATE TABLE `msb_essay_tag` (
// `tag_id` bigint unsigned NOT NULL,
// `essay_id` bigint unsigned NOT NULL,
// PRIMARY KEY (`tag_id`,`essay_id`),
// KEY `fk_msb_essay_tag_essay` (`essay_id`),
// CONSTRAINT `fk_msb_essay_tag_essay` FOREIGN KEY (`essay_id`) REFERENCES `msb_essay` (`id`),
// CONSTRAINT `fk_msb_essay_tag_tag` FOREIGN KEY (`tag_id`) REFERENCES `msb_tag` (`id`)
//) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
log.Println("migrate successful")
}
// 添加关联
func AssocAppend() {
// A一对多的关系, Author 1:n Essay
// 创建测试数据
var a Author
a.Name = "一位作者"
if err := DB.Create(&a).Error; err != nil {
log.Println(err)
}
log.Println("a:", a.ID)
var e1, e2 Essay
e1.Subject = "一篇内容"
//e1.AuthorID = a.ID
e2.Subject = "另一篇内容"
if err := DB.Create([]*Essay{&e1, &e2}).Error; err != nil {
log.Println(err)
}
log.Println("e1, e2: ", e1.ID, e2.ID)
// 添加关联
if err := DB.Model(&a).Association("Essays").Append([]Essay{e1}); err != nil {
log.Println(err)
}
fmt.Println(len(a.Essays))
// 基于当前的基础上,添加关联
if err := DB.Model(&a).Association("Essays").Append([]Essay{e2}); err != nil {
log.Println(err)
}
fmt.Println(len(a.Essays))
// 添加后a模型对象的Essays字段自动包含了关联的Essay模型
//fmt.Println(a.Essays)
// B: Essay M:N TAg
var t1, t2, t3 Tag
t1.Title = "Go"
t2.Title = "GORM"
t3.Title = "Ma"
if err := DB.Create([]*Tag{&t1, &t2, &t3}).Error; err != nil {
log.Println(err)
}
log.Println("t1, t2, t3: ", t1.ID, t2.ID, t3.ID)
// e1 t1, t3
// e2 t1, t2, t3
if err := DB.Model(&e1).Association("Tags").Append([]Tag{t1, t3}); err != nil {
log.Println(err)
}
if err := DB.Model(&e2).Association("Tags").Append([]Tag{t1, t2, t3}); err != nil {
log.Println(err)
}
// 关联表查看
// mysql> select * from msb_essay_tag;
//+--------+----------+
//| tag_id | essay_id |
//+--------+----------+
//| 1 | 12 |
//| 3 | 12 |
//| 1 | 13 |
//| 2 | 13 |
//| 3 | 13 |
//+--------+----------+
// C, Belongs To. Essay N:1 Author
var e3 Essay
e3.Subject = "第三篇内容"
if err := DB.Create([]*Essay{&e3}).Error; err != nil {
log.Println(err)
}
log.Println("e3: ", e3.ID)
log.Println(e3.Author)
// 关联
if err := DB.Model(&e3).Association("Author").Append(&a); err != nil {
log.Println(err)
}
log.Println(e3.Author.ID)
// 对一的关联,会导致关联被更新
var a2 Author
a2.Name = "另一位作者"
if err := DB.Create(&a2).Error; err != nil {
log.Println(err)
}
log.Println("a2:", a2.ID)
if err := DB.Model(&e3).Association("Author").Append(&a2); err != nil {
log.Println(err)
}
log.Println(e3.Author.ID)
}
func AssocReplace() {
// A. 替换
// 创建测试数据
var a Author
a.Name = "一位作者"
if err := DB.Create(&a).Error; err != nil {
log.Println(err)
}
log.Println("a:", a.ID)
var e1, e2, e3 Essay
e1.Subject = "一篇内容"
e2.Subject = "另一篇内容"
e3.Subject = "第三篇内容"
if err := DB.Create([]*Essay{&e1, &e2, &e3}).Error; err != nil {
log.Println(err)
}
log.Println("e1, e2, e3: ", e1.ID, e2.ID, e3.ID)
// 添加关联
if err := DB.Model(&a).Association("Essays").Replace([]Essay{e1, e3}); err != nil {
log.Println(err)
}
fmt.Println(len(a.Essays))
// 基于当前的基础上,添加关联
if err := DB.Model(&a).Association("Essays").Replace([]Essay{e2, e3}); err != nil {
log.Println(err)
}
fmt.Println(len(a.Essays))
}
func AssocDelete() {
// B. 删除,外键的
// 创建测试数据
var a Author
a.Name = "一位作者"
if err := DB.Create(&a).Error; err != nil {
log.Println(err)
}
log.Println("a:", a.ID)
var e1, e2, e3 Essay
e1.Subject = "一篇内容"
e2.Subject = "另一篇内容"
e3.Subject = "第三篇内容"
if err := DB.Create([]*Essay{&e1, &e2, &e3}).Error; err != nil {
log.Println(err)
}
log.Println("e1, e2, e3: ", e1.ID, e2.ID, e3.ID)
// 添加关联
if err := DB.Model(&a).Association("Essays").Replace([]Essay{e1, e2, e3}); err != nil {
log.Println(err)
}
fmt.Println(len(a.Essays))
if err := DB.Model(&a).Association("Essays").Delete([]Essay{e1, e3}); err != nil {
log.Println(err)
}
fmt.Println(len(a.Essays))
fmt.Println("------------------------")
// B. 删除,多对多,关联表
var t1, t2, t3 Tag
t1.Title = "Go"
t2.Title = "GORM"
t3.Title = "Ma"
if err := DB.Create([]*Tag{&t1, &t2, &t3}).Error; err != nil {
log.Println(err)
}
log.Println("t1, t2, t3: ", t1.ID, t2.ID, t3.ID)
// e1 t1, t3
// e2 t1, t2, t3
if err := DB.Model(&e1).Association("Tags").Append([]Tag{t1, t2, t3}); err != nil {
log.Println(err)
}
fmt.Println(len(e1.Tags))
if err := DB.Model(&e1).Association("Tags").Delete([]Tag{t1, t3}); err != nil {
log.Println(err)
}
fmt.Println(len(e1.Tags))
// C. 清空关联
if err := DB.Model(&e1).Association("Tags").Clear(); err != nil {
log.Println(err)
}
fmt.Println(len(e1.Tags))
}
func AssocFind() {
//
e := Essay{}
DB.First(&e, 18)
// 查询关联的tags
//var ts []Tag
if err := DB.Model(&e).Association("Tags").Find(&e.Tags); err != nil {
log.Println(err)
}
log.Println(e.Tags)
// 子句要写在Association()方法前面
if err := DB.Model(&e).
Where("tag_id > ?", 7).
Order("tag_id DESC").
Association("Tags").Find(&e.Tags); err != nil {
log.Println(err)
}
log.Println(e.Tags)
// 查询关联的模型的数量
count := DB.Model(&e).Association("Tags").Count()
log.Println("count:", count)
}
func AssocSave() {
var t1 Tag
DB.First(&t1, 10)
e := Essay{
Subject: "一个组合的Save",
Author: Author{Name: "马士兵"},
Tags: []Tag{
t1,
{Title: "Ma"},
{Title: "GORM"},
},
}
if err := DB.Save(&e).Error; err != nil {
log.Println(err)
}
log.Printf("%+v\n", e)
}
// Preload
func AssocPreload() {
// A.直接一步查询Author对应的Essays
a := Author{}
if err := DB.
Preload("Essays").
First(&a, 1).Error; err != nil {
log.Fatalln(err)
}
// [3.840ms] [rows:2] SELECT * FROM `msb_essay` WHERE `msb_essay`.`author_id` = 1 AND `msb_essay`.`deleted_at` IS NULL
// [13.014ms] [rows:1] SELECT * FROM `msb_author` WHERE `msb_author`.`id` = 1 AND `msb_author`.`deleted_at` IS NULL ORDER BY `msb_author`.`id` LIMIT 1
log.Println(a.Essays)
log.Println("--------------------")
// B.支持条件过滤
if err := DB.
Preload("Essays", "id IN ?", []uint{2, 3, 4}).
First(&a, 1).Error; err != nil {
log.Fatalln(err)
}
// [3.217ms] [rows:1] SELECT * FROM `msb_essay` WHERE `msb_essay`.`author_id` = 1 AND id IN (2,3,4) AND `msb_essay`.`deleted_at` IS NULL
log.Println(a.Essays)
log.Println("-----------------------")
// C. 支持多次链式调用,同时预加载多个关联
e := Essay{}
if err := DB.
//Preload("Author").
//Preload("EssayMate").
//Preload("Tags").
Preload(clause.Associations).
First(&e, 1).Error; err != nil {
log.Fatalln(err)
}
log.Println(e)
// [2.776ms] [rows:1] SELECT * FROM `msb_author` WHERE `msb_author`.`id` = 1 AND `msb_author`.`deleted_at` IS NULL
// [10.398ms] [rows:0] SELECT * FROM `msb_essay_mate` WHERE `msb_essay_mate`.`essay_id` = 1 AND `msb_essay_mate`.`deleted_at` IS NULL
// [3.260ms] [rows:2] SELECT * FROM `msb_essay_tag` WHERE `msb_essay_tag`.`essay_id` = 1
// [3.264ms] [rows:2] SELECT * FROM `msb_tag` WHERE `msb_tag`.`id` IN (1,3) AND `msb_tag`.`deleted_at` IS NULL
// [28.067ms] [rows:1] SELECT * FROM `msb_essay` WHERE `msb_essay`.`id` = 1 AND `msb_essay`.`deleted_at` IS NULL ORDER BY `msb_essay`.`id` LIMIT 1
}
// 多级
func AssocLevelPreload() {
a := Author{}
if err := DB.
//Preload("Essays").
// 多级关联
Preload("Essays.Tags").
First(&a, 1).Error; err != nil {
log.Fatalln(err)
}
// [3.843ms] [rows:5] SELECT * FROM `msb_essay_tag` WHERE `msb_essay_tag`.`essay_id` IN (1,2)
// [3.284ms] [rows:3] SELECT * FROM `msb_tag` WHERE `msb_tag`.`id` IN (1,3,2) AND `msb_tag`.`deleted_at` IS NULL
// [10.396ms] [rows:2] SELECT * FROM `msb_essay` WHERE `msb_essay`.`author_id` = 1 AND `msb_essay`.`deleted_at` IS NULL
// [17.609ms] [rows:1] SELECT * FROM `msb_author` WHERE `msb_author`.`id` = 1 AND `msb_author`.`deleted_at` IS NULL ORDER BY `msb_author`.`id` LIMIT 1
log.Println(a.Essays[0].Tags)
log.Println(a.Essays[1].Tags)
}
func AssocOperate() {
// 创建测试数据
var a Author
a.Name = "一位作者"
if err := DB.Create(&a).Error; err != nil {
log.Println(err)
}
log.Println("a:", a.ID)
var e1, e2 Essay
e1.Subject = "一篇内容"
//e1.AuthorID = a.ID
e2.Subject = "另一篇内容"
if err := DB.Create([]*Essay{&e1, &e2}).Error; err != nil {
log.Println(err)
}
log.Println("e1, e2: ", e1.ID, e2.ID)
// 基于当前的基础上,添加关联
if err := DB.Model(&a).Association("Essays").Append([]Essay{e1, e2}); err != nil {
log.Println(err)
}
fmt.Println(len(a.Essays))
// 删除a,注意essays的处理
if err := DB.Unscoped().Delete(&a).Error; err != nil {
log.Fatalln(err)
}
}

@ -0,0 +1,39 @@
package gormExample
import "testing"
func TestStdAssocModel(t *testing.T) {
StdAssocModel()
}
func TestAssocAppend(t *testing.T) {
AssocAppend()
}
func TestAssocReplace(t *testing.T) {
AssocReplace()
}
func TestAssocDelete(t *testing.T) {
AssocDelete()
}
func TestAssocFind(t *testing.T) {
AssocFind()
}
func TestAssocSave(t *testing.T) {
AssocSave()
}
func TestAssocPreload(t *testing.T) {
AssocPreload()
}
func TestAssocLevelPreload(t *testing.T) {
AssocLevelPreload()
}
func TestAssocOperate(t *testing.T) {
AssocOperate()
}

@ -24,14 +24,14 @@ type Content struct {
AuthorID uint
}
// Author模型
type Author struct {
gorm.Model
Status int
Name string
Email string
}
//// Author模型
//type Author struct {
// gorm.Model
// Status int
//
// Name string
// Email string
//}
type ContentStrPK struct {
ID string `gorm:"primaryKey"`
@ -81,3 +81,91 @@ func (c *Content) AfterFind(db *gorm.DB) error {
return nil
}
// 作者模型
// Author模型
type Author struct {
gorm.Model
Status int
Name string
Email string
// 积分
Points int
// 拥有多个论文内容
// has many
// 默认关联
Essays []Essay `gorm:"constraint:OnDelete:SET NULL;"`
// 第一作者论文列表
FirstEssays []Essay `gorm:"foreignKey:FirstAuthorID;references:;"`
// 第二作者论文列表
SecondEssays []Essay `gorm:"foreignKey:SecondAuthorID;references:;"`
}
// CREATE TABLE `msb_essay` (
// `id` bigint unsigned NOT NULL AUTO_INCREMENT,
// `first_author_id` bigint unsigned DEFAULT NULL,
// `second_author_id` bigint unsigned DEFAULT NULL,
// `author_id` bigint unsigned DEFAULT NULL,
// PRIMARY KEY (`id`),
// KEY `idx_msb_essay_deleted_at` (`deleted_at`),
// KEY `fk_msb_author_essays` (`author_id`),
// KEY `fk_msb_author_first_essays` (`first_author_id`),
// KEY `fk_msb_author_second_essays` (`second_author_id`),
// CONSTRAINT `fk_msb_author_essays` FOREIGN KEY (`author_id`) REFERENCES `msb_author` (`id`),
// CONSTRAINT `fk_msb_author_first_essays` FOREIGN KEY (`first_author_id`) REFERENCES `msb_author` (`id`),
// CONSTRAINT `fk_msb_author_second_essays` FOREIGN KEY (`second_author_id`) REFERENCES `msb_author` (`id`)
//) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
// 论文内容
type Essay struct {
gorm.Model
Subject string
Content string
// 自定义关联字段
FirstAuthorID *uint
SecondAuthorID *uint
FirstAuthor Author `gorm:"foreignKey:FirstAuthorID;references:;"`
SecondAuthor Author `gorm:"foreignKey:SecondAuthorID;references:;"`
// 外键字段
AuthorID *uint
// 属于某个作者
// belongs to
Author Author
// 拥有一个论文元信息
// has one
EssayMate EssayMate
// 拥有多个Tag
// many to many
Tags []Tag `gorm:"many2many:essay_tag"`
}
// 论文元信息
type EssayMate struct {
gorm.Model
Keyword string
Description string
// 外键字段
EssayID *uint
// 属于一个论文内容,比较少用
//Essay *Essay
}
type Tag struct {
gorm.Model
Title string
// 拥有多个Essay
// many to many
Essays []Essay `gorm:"many2many:essay_tag"`
}

@ -72,7 +72,6 @@ func SessionNew() {
db.Where("likes < ?", 5).Find(&cs2)
// [3.846ms] [rows:0] SELECT * FROM `msb_content` WHERE views > 100 AND likes < 5 AND `msb_content`.`deleted_at` IS NULL
}
func SessionOptions() {
// Skip Hook
//db := DB.Session(&gorm.Session{

@ -0,0 +1,263 @@
package gormExample
import (
"errors"
"gorm.io/gorm"
"log"
)
func TXDemo() {
// 初始化测试数据
if err := DB.AutoMigrate(&Author{}); err != nil {
log.Fatalln(err)
}
var a1, a2 Author
a1.Name = "库里"
a2.Name = "莫兰特"
a1.Points = 11600
a2.Points = 200
if err := DB.Create([]*Author{&a1, &a2}).Error; err != nil {
log.Fatalln(err)
}
// 事务操作
// a1 赠送 a2 2000 积分
p := 2000
// 开始事务
tx := DB.Begin()
// 有时需要考虑数据库是否支持事务的情景
if tx.Error != nil {
log.Fatalln(tx.Error)
}
// 执行赠送操作
a1.Points -= p
a2.Points += p
// 1执行SQL可能导致的错误
if err := tx.Save(&a1).Error; err != nil {
tx.Rollback()
return
}
if err := tx.Save(&a2).Error; err != nil {
// 回滚事务
tx.Rollback()
return
}
// 2业务逻辑可能导致的错误
// 要求author的积分不能为负数
if a1.Points < 0 || a2.Points < 0 {
log.Println("a1.Points < 0 || a2.Points < 0")
// 回滚事务
if err := tx.Rollback().Error; err != nil {
log.Fatalln(err)
}
}
// 提交事务
if err := tx.Commit().Error; err != nil {
log.Fatalln(err)
}
// 决定回滚还是提交,集中的处理错误风格
//if err1 != nil || err2 != nil {
// tx.Rollback()
//} else {
// tx.Commit()
//}
}
func TXCallback() {
// 初始化测试数据
if err := DB.AutoMigrate(&Author{}); err != nil {
log.Fatalln(err)
}
var a1, a2 Author
a1.Name = "库里"
a2.Name = "莫兰特"
a1.Points = 1600
a2.Points = 200
if err := DB.Create([]*Author{&a1, &a2}).Error; err != nil {
log.Fatalln(err)
}
log.Println(a1.ID, a2.ID)
// 实现事务
if err := DB.Transaction(func(tx *gorm.DB) error {
// a1 赠送 a2 2000 积分
p := 200
// 执行赠送操作
a1.Points -= p
a2.Points += p
// 1执行SQL可能导致的错误
if err := tx.Save(&a1).Error; err != nil {
return err
}
if err := tx.Save(&a2).Error; err != nil {
return err
}
// 2业务逻辑可能导致的错误
// 要求author的积分不能为负数
if a1.Points < 0 || a2.Points < 0 {
return errors.New("a1.Points < 0 || a2.Points < 0")
}
// nil 的返回,会导致事务提交
return nil
}); err != nil {
// 返回错误,为了后续的业务逻辑处理
// 为了通知我们,事务成功还是失败
// 返回错误,不影响事务的提交和回滚
log.Println(err)
}
}
func TXNested() {
// 初始化测试数据
if err := DB.AutoMigrate(&Author{}); err != nil {
log.Fatalln(err)
}
var a1, a2, a3 Author
a1.Name = "库里"
a2.Name = "莫兰特"
a3.Name = "欧文"
a1.Points = 1600
a2.Points = 200
a3.Points = 4000
if err := DB.Create([]*Author{&a1, &a2, &a3}).Error; err != nil {
log.Fatalln(err)
}
log.Println(a1.ID, a2.ID, a3.ID)
// 实现事务
if err := DB.Transaction(func(tx *gorm.DB) error {
// a1 赠送 a2 2000 积分
p := 20000
// 执行赠送操作
// a2 多了积分
a2.Points += p
if err := tx.Save(&a2).Error; err != nil {
return err
}
// a1 赠送,使用嵌套事务完成
errA1 := tx.Transaction(func(tx *gorm.DB) error {
a1.Points -= p
// 1执行SQL可能导致的错误
if err := tx.Save(&a1).Error; err != nil {
return err
}
if a1.Points < 0 {
return errors.New("a1.Points < 0")
}
// 没有错误成功
return nil
})
// a1 发送失败才需要a3
if errA1 != nil {
// a3 赠送,使用嵌套事务完成
errA3 := DB.Transaction(func(tx *gorm.DB) error {
a3.Points -= p
if err := tx.Save(&a3).Error; err != nil {
return err
}
if a3.Points < 0 {
return errors.New("a3.Points < 0")
}
return nil
})
// a3 同样失败
if errA3 != nil {
return errors.New("a1 and a3 all send points failed")
}
}
// nil 的返回,会导致事务提交
return nil
}); err != nil {
// 返回错误,为了后续的业务逻辑处理
// 为了通知我们,事务成功还是失败
// 返回错误,不影响事务的提交和回滚
log.Println(err)
}
}
func TXSavePoint() {
// 初始化测试数据
if err := DB.AutoMigrate(&Author{}); err != nil {
log.Fatalln(err)
}
var a1, a2, a3 Author
a1.Name = "库里"
a2.Name = "莫兰特"
a3.Name = "欧文"
a1.Points = 1600
a2.Points = 200
a3.Points = 4000
if err := DB.Create([]*Author{&a1, &a2, &a3}).Error; err != nil {
log.Fatalln(err)
}
log.Println(a1.ID, a2.ID, a3.ID)
// 事务操作
// a1 赠送 a2 2000 积分
p := 20000
// 开始事务
tx := DB.Begin()
// 有时需要考虑数据库是否支持事务的情景
if tx.Error != nil {
log.Fatalln(tx.Error)
}
// 执行赠送操作
// a2 得到积分
a2.Points += p
// 1执行SQL可能导致的错误
if err := tx.Save(&a2).Error; err != nil {
tx.Rollback()
return
}
// 逻辑记录发送points是否成功
var flagSend bool
// a1 先给 a2 send
// 设置一个 savepoint
tx.SavePoint("beforeA1")
a1.Points -= p
if err := tx.Save(&a1).Error; err != nil || a1.Points < 0 {
// 回滚到 beforeA1
tx.RollbackTo("beforeA1")
// a3 to a2
tx.SavePoint("beforeA3")
a3.Points -= p
if err := tx.Save(&a3).Error; err != nil || a3.Points < 0 {
// 回滚到 beforeA3
tx.RollbackTo("beforeA3")
} else {
flagSend = true
}
} else {
flagSend = true
}
// 判定发送是否成功
if flagSend {
// 提交事务
if err := tx.Commit().Error; err != nil {
log.Fatalln(err)
}
} else {
// 回滚事务
tx.Rollback()
}
}

@ -0,0 +1,19 @@
package gormExample
import "testing"
func TestTXDemo(t *testing.T) {
TXDemo()
}
func TestTXCallback(t *testing.T) {
TXCallback()
}
func TestTXNested(t *testing.T) {
TXNested()
}
func TestTXSavePoint(t *testing.T) {
TXSavePoint()
}
Loading…
Cancel
Save