Go gorm库(详细版)

目录

01. 什么是ORM

02. 环境搭建

03. 连接数据库

高级设置

gorm 的命名策略

创建表

日志显示

04. 模型定义

定义一张表

自动生成表结构

修改表字段大小

字段标签

05. 单表查询

5.1 表结构

5.2 添加单条记录

5.3 批量插入

5.4 单条数据查询

5.5 根据主键查询

5.6 根据结构体进行查询

5.7 获取查询的结构

5.8 查询多条记录并返回Json数据

5.9 根据主键列表去查询

06. 更新数据

6.1 Save 保存所有字段

Select更新指定字段​编辑

6.2 Update批量更新

6.3 Updates更新

07. 删除数据

08. 添加钩子函数(HOOK)

09. Gorm高级查询

9.01 Where查询

9.02 Select 选择字段

9.03 排序

9.04 分页查询

9.05 去重

​编辑9.06 分组查询

9.07 gorm执行原生sql

9.08 子查询

9.09 命名参数

9.10 从Find到Map

9.11 查询引用Scope

10. 一对多关系

10.1 表结构建立

重写外键关联

10.2 添加数据

外键添加

10.3 查询数据

预加载

嵌套预加载

带条件的预加载

自定义预加载

10.4 删除数据

级联删除

清除外键关系

11. 一对一关系

表结构搭建

添加记录

查询

删除

12. 多对多关系

12.1 表结构搭建

12.2 添加

添加文章,并创建标签

创建文章,添加已有标签

12.3 查询

12.4 更新

12.5 多对多自定义连接表(第三张表)

12.5.1 表结构及生成

12.5.2 操作案例

1. 添加文章并添加标签,并自动关联

2. 添加文章,关联已有标签

3. 给已有文章关联标签

4. 替换已有文章的标签

5. 查询文章列表,显示标签

SetupJoinTable

12.5.3 自定义连接表主键

生成表结构

12.5.4 操作连接表(自定义连接表的时候)

13. 自定义数据类型

13.1 存储json

插入数据

查询数据

13.2 存储数组

13.3 枚举类型

枚举1.0

枚举2.0

枚举3.0(用这个)

在grom中使用

14. gorm 事务

14.1 普通事务

14.2 手动事务


01. 什么是ORM

02. 环境搭建

go mod init 文件名

go get gorm.io/driver/mysql //mysql的驱动

go get "gorm.io/gorm"

03. 连接数据库

package main

import (
	"fmt"

	"gorm.io/driver/mysql"
	"gorm.io/gorm"
)

// 定义一个全局变量db,用于后面数据库的读写操作,通常就放在全局里面
var DB *gorm.DB

func init() {
	username := "root"       //账号
	password := "password"   //密码
	host := "IP"             //数据库地址
	port := "3306"           //端口
	Dnname := "dtbase"       //数据库名
	timeout := "10s"         //连接超时,10s

	//root:root@tcp(127.0.0.1:3306)/test?
	dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?timeout=%s", username, password, host, port, Dnname, timeout)
	//连接mysql,获得DB类型实例,用于后面数据库的读写操作
	db, err := gorm.Open(mysql.Open(dsn))
	if err != nil {
		panic("连接数据库失败,error=" + err.Error())
	}
	DB = db

	//连接成功
	fmt.Println("连接数据库成功")
}

func main() {

}

高级设置

是在open的地方设置

gorm 的命名策略

创建表

注意虽然我们的结构为Student,但是表名却是studens,并且字段全小写,至于为什么是这样,就是我们上面gorm命名规则的原因了

日志显示

gorm默认日志是只打印错误和慢sql,我们可以设置日志的显示等级

可以设置日志等级为info,但是这样显示会很占用空间

推荐:

04. 模型定义

模型是标准的 struct ,由Go的基本数据类型,实现了Scanner和Valuer接口的自定义类型及其指针或别名组成

定义一张表

PS:小写属性是不会生成字段的

自动生成表结构

AutoMigrate的逻辑是只新增,不擅长,不修改(大小会修改)

例如将 Name 修改为 Name1,进行迁移,会多出一个name1的字段

新增Email和改Name为Name1

修改表字段大小

有两种方式

下面那种样例

字段标签

05. 单表查询

5.1 表结构

5.2 添加单条记录

添加记录就是实例化结构体

有两条是因为点了两下哈

如果此时

那么他们分别是空字符串和null

这就是指针的好处,当然email也可以直接传nil因为他是指针

还可以打印s1看看

5.3 批量插入

Creat方法还可以用于插入多条记录

5.4 单条数据查询

会查询第一个数据

Take就是传统的:SELECT * FROM 'students' LIMIT 1

而Frist和Last是按照主键去查询的:SELECT * FROM 'students' ORDER BY 'studens'.'id' LIMIT 1

5.5 根据主键查询

注意这是前端传进来的话一定要用?拼接,不能fprintf,这样可以有效防止sql注入

5.6 根据结构体进行查询

但是注意只能根据主键查询

5.7 获取查询的结构

5.8 查询多条记录并返回Json数据

5.9 根据主键列表去查询

根据其他条件就可以

DB.Find(&studentList,"name in ?",[]string{"yuanlai","chenchen"})

06. 更新数据

有三个操作可以进行,Save,Uptate,Updates

6.1 Save 保存所有字段

用于单个记录的全字段更新,他会保存所有的字段,即使零值也会保存 

相当于:UPDATE 'students_two' SET 'name'='y145','age'=23,'gender'=true,'emaI'=2777137742@qq.com WHERE 'id'=1

Select更新指定字段

6.2 Update批量更新

或者Model

6.3 Updates更新

可以传结构体

也可以传map

07. 删除数据

08. 添加钩子函数(HOOK)

比如再插入一条数据之前,我想要做一点事情

其实就是实现一个 BeforeCreate 的方法

在实现BeforeCreate之后

插入这个

09. Gorm高级查询

9.01 Where查询

我们重构一些数据

上面用了一个函数,传string返回他的地址,这样插入的时候好看些

然后正文,这个Where就等价于mysql的where

9.02 Select 选择字段

因为直接Find是select * 比较耗费性能

9.03 排序

9.04 分页查询

先看纯sql的分页查询

9.05 去重

 先看sql去重

gorm去重(Scan就是把前面得到的结果给Scan里面的结构体)


9.06 分组查询

sql拼接名字

gorm写法

9.07 gorm执行原生sql

就上面例子来说,就是DB.Raw("saw_sql").Scan(&groupList)

9.08 子查询

子查询就是使用上次查询的结果来作为这次查询的参数

9.09 命名参数

我们之前是?,但是如果查询语句比较多,看的就不直观,orm就可以提供像@name(给参数命名)这样的方式

9.10 从Find到Map

我们每次查询的时候,都要写一个变量去接收查到的值,感觉很麻烦

(之前的 var students []Student,这个students用来Find(&students)传参的)

我们就可以用一个map来接收

emmmmmm好像没什么区别

9.11 查询引用Scope

10. 一对多关系

10.1 表结构建立

对于外键的命名,我们这里就必须要叫做UserID,其他的就不可以

或者不一样的话,我们就需要重写外键关联

重写外键关联

这就要注意,两边都要加上

10.2 添加数据

创建用户的时候创建文章

创建文章的时候,再去关联用户

又或者,但是这样就会又创建一个新的用户去关联他

又或者,不创建user,用已经有的

外键添加

其中Association和Append的方式更常用

10.3 查询数据

预加载

嵌套预加载

带条件的预加载

自定义预加载

10.4 删除数据

级联删除

清除外键关系

11. 一对一关系

表结构搭建

PS:UserInfo里面用指针是因为如果不用,就是和User相互引用了

添加记录

查询

删除

删除和一对多是一样的

12. 多对多关系

多对多关系,需要用第三张表存储两张表的关系

12.1 表结构搭建

12.2 添加

添加文章,并创建标签

查看表

创建文章,添加已有标签

12.3 查询

查询文章,显示文章的标签列表

查询标签,显示文章列表

12.4 更新

之前的方式

gorm提供的方法

让tag1替换为tag2

12.5 多对多自定义连接表(第三张表)

默认的连接表,只有双方的主键id,展示不了更多信息了,比如我们现在想要连接表里面添加数据的时候加上添加的时间,这个时候就需要自定义连接表,这就是他的意义

12.5.1 表结构及生成

注意`form:"many2many:article_tags"` article_tags这个名字是对应ArticleTag的,这个是对应的然后加上 _ 和 s,要是想改gorm里面的名字,那么AticleTag也要改

// 设置Article的Tags表为ArticleTag
DB.SetupJoinTable(&Article{}, "Tags", &ArticleTag{})
// 如果tag要反向应用Article,那么也得加上
// DB.SetupJoinTable(&Tag{}, "Articles", &ArticleTag{})
err := DB.AutoMigrate(&Article{}, &Tag{}, &ArticleTag{})
fmt.Println(err)

12.5.2 操作案例

举一些简单的例子

  1. 添加文章并添加标签,并自动关联

  2. 添加文章,关联已有标签

  3. 给已有文章关联标签

  4. 替换已有文章的标签

  5. 添加文章并添加标签,并自动关联

1. 添加文章并添加标签,并自动关联
DB.SetupJoinTable(&Article{}, "Tags", &ArticleTag{})  // 要设置这个,才能走到我们自定义的连接表
DB.Create(&Article{
  Title: "flask零基础入门",
  Tags: []Tag{
    {Name: "python"},
    {Name: "后端"}, 
    {Name: "web"},
  },
})
// CreatedAt time.Time 由于我们设置的是CreatedAt,gorm会自动填充当前时间,
// 如果是其他的字段,需要使用到ArticleTag 的添加钩子 BeforeCreate
2. 添加文章,关联已有标签
DB.SetupJoinTable(&Article{}, "Tags", &ArticleTag{})
var tags []Tag
DB.Find(&tags, "name in ?", []string{"python", "web"})
DB.Create(&Article{
  Title: "flask请求对象",
  Tags:  tags,
})
3. 给已有文章关联标签
DB.SetupJoinTable(&Article{}, "Tags", &ArticleTag{})
article := Article{
  Title: "django基础",
}
DB.Create(&article)
var at Article
var tags []Tag
DB.Find(&tags, "name in ?", []string{"python", "web"})
DB.Take(&at, article.ID).Association("Tags").Append(tags)
4. 替换已有文章的标签
var article Article
var tags []Tag
DB.Find(&tags, "name in ?", []string{"后端"})
DB.Take(&article, "title = ?", "django基础")
DB.Model(&article).Association("Tags").Replace(tags)
5. 查询文章列表,显示标签
var articles []Article
DB.Preload("Tags").Find(&articles)
fmt.Println(articles)
SetupJoinTable

添加和更新的时候得用这个

这样才能走自定义的连接表,以及走它的钩子函数

查询则不需要这个

(如果添加和更新不加,就我们这个情况的话时间就不会添加上去,因为他就不会走SetupJoinTable的那张表,而是默认的那张表,而那张表里面则没有时间,所以不会添加时间,但是不会添加失败)

12.5.3 自定义连接表主键

这个功能还是很有用的,例如你的文章表 可能叫ArticleModel,你的标签表可能叫TagModel

那么按照gorm默认的主键名,那就分别是ArticleModelID,TagModelID,太长了,根本就不实用

这个地方,官网给的例子看着也比较迷,不过我已经跑通了

主要是要修改这两项

joinForeignKey 连接的主键id

JoinReferences 关联的主键id

type ArticleModel struct {
  ID    uint
  Title string
  Tags  []TagModel `gorm:"many2many:article_tags;joinForeignKey:ArticleID;JoinReferences:TagID"`
}

type TagModel struct {
  ID       uint
  Name     string
  Articles []ArticleModel `gorm:"many2many:article_tags;joinForeignKey:TagID;JoinReferences:ArticleID"`
}

type ArticleTagModel struct {
  ArticleID uint `gorm:"primaryKey"` // article_id
  TagID     uint `gorm:"primaryKey"` // tag_id
  CreatedAt time.Time
}
生成表结构
DB.SetupJoinTable(&ArticleModel{}, "Tags", &ArticleTagModel{})
DB.SetupJoinTable(&TagModel{}, "Articles", &ArticleTagModel{})
err := DB.AutoMigrate(&ArticleModel{}, &TagModel{}, &ArticleTagModel{})
fmt.Println(err)

添加,更新,查询操作和上面的都是一样

12.5.4 操作连接表(自定义连接表的时候)

如果通过一张表去操作连接表,这样会比较麻烦

比如查询某篇文章关联了哪些标签

或者是举个更通用的例子,用户和文章,某个用户在什么时候收藏了哪篇文章

无论是通过用户关联文章,还是文章关联用户都不太好查

最简单的就是直接查连接表

type UserModel struct {
  ID       uint
  Name     string
  Collects []ArticleModel `gorm:"many2many:user_collect_models;joinForeignKey:UserID;JoinReferences:ArticleID"`
}

type ArticleModel struct {
  ID    uint
  Title string
  // 这里也可以反向引用,根据文章查哪些用户收藏了
}

// UserCollectModel 用户收藏文章表
type UserCollectModel struct {
  UserID    uint `gorm:"primaryKey"` // article_id
  ArticleID uint `gorm:"primaryKey"` // tag_id
  CreatedAt time.Time
}

func main() {
  DB.SetupJoinTable(&UserModel{}, "Collects", &UserCollectModel{})
  err := DB.AutoMigrate(&UserModel{}, &ArticleModel{}, &UserCollectModel{})
  fmt.Println(err)
}

常用的操作就是根据用户查收藏的文章列表

var user UserModel
DB.Preload("Collects").Take(&user, "name = ?", "枫枫")
fmt.Println(user)

但是这样不太好做分页,并且也拿不到收藏文章的时间

var collects []UserCollectModel
DB.Find(&collects, "user_id = ?", 2)
fmt.Println(collects)

这样虽然可以查到用户id,文章id,收藏的时间,但是搜索只能根据用户id搜,返回也拿不到用户名,文章标题等

我们需要改一下表结构,不需要重新迁移,加一些字段

type UserModel struct {
  ID       uint
  Name     string
  Collects []ArticleModel `gorm:"many2many:user_collect_models;joinForeignKey:UserID;JoinReferences:ArticleID"`
}

type ArticleModel struct {
  ID    uint
  Title string
}

// UserCollectModel 用户收藏文章表
type UserCollectModel struct {
  UserID       uint         `gorm:"primaryKey"` // article_id
  UserModel    UserModel    `gorm:"foreignKey:UserID"`
  ArticleID    uint         `gorm:"primaryKey"` // tag_id
  ArticleModel ArticleModel `gorm:"foreignKey:ArticleID"`
  CreatedAt    time.Time
}

查询

var collects []UserCollectModel

var user UserModel
DB.Take(&user, "name = ?", "枫枫")
// 这里用map的原因是如果没查到,那就会查0值,如果是struct,则会忽略零值,全部查询
DB.Debug().Preload("UserModel").Preload("ArticleModel").Where(map[string]any{"user_id": user.ID}).Find(&collects)

for _, collect := range collects {
  fmt.Println(collect)
}

13. 自定义数据类型

很多情况下我们存储到数据库中的数据是多变的

例如我需要存储json或者是数组

然后很多数据库并不能直接存储这些数据类型,我们就需要自定义数据类型

自定义的数据类型必须实现 Scanner 和 Valuer 接口,以便让 GORM 知道如何将该类型接收、保存到数据库

gorm中自定义数据类型无外乎就两个方法

在数据入库的时候要转换为什么数据,已经出库的时候数据变成什么样子

13.1 存储json

存储json可能是经常使用到的

我们需要定义一个结构体,在入库的时候,把它转换为[]byte类型,查询的时候把它转换为结构体

type Info struct {
  Status string `json:"status"`
  Addr   string `json:"addr"`
  Age    int    `json:"age"`
}

// Scan 从数据库中读取出来
func (i *Info) Scan(value interface{}) error {
  bytes, ok := value.([]byte)
  if !ok {
    return errors.New(fmt.Sprint("Failed to unmarshal JSONB value:", value))
  }
  err := json.Unmarshal(bytes, i)
  return err
}

// Value 存入数据库
func (i Info) Value() (driver.Value, error) {
  return json.Marshal(i)
}

type AuthModel struct {
  ID   uint
  Name string
  Info Info `gorm:"type:string"`
}

func main() {
  DB.AutoMigrate(&AuthModel{})
}

插入数据

DB.Debug().Create(&AuthModel{
  Name: "枫枫",
  Info: Info{
    Status: "success",
    Addr:   "湖南省长沙市",
    Age:    21,
  },
})

// INSERT INTO `auth_models` (`name`,`info`) VALUES ('枫枫','{"status":"success","addr":"湖南省长沙市","age":21}')

查询数据

var auth AuthModel
DB.Take(&auth, "name = ?", "枫枫")
fmt.Println(auth)

13.2 存储数组

很多时候存储数组也是很常见的

最简单的方式就是存json

type Array []string

// Scan 从数据库中读取出来
func (arr *Array) Scan(value interface{}) error {
  bytes, ok := value.([]byte)
  if !ok {
    return errors.New(fmt.Sprint("Failed to unmarshal JSONB value:", value))
  }
  err := json.Unmarshal(bytes, arr)
  return err
}

// Value 存入数据库
func (arr Array) Value() (driver.Value, error) {
  return json.Marshal(arr)
}

type HostModel struct {
  ID    uint   `json:"id"`
  IP    string `json:"ip"`
  Ports Array  `gorm:"type:string" json:"ports"`
}

func main() {
  //DB.AutoMigrate(&HostModel{})

  //DB.Create(&HostModel{
  //  IP:    "192.168.200.21",
  //  Ports: []string{"80", "8080"},
  //})
  var host HostModel
  DB.Take(&host, 1)
  fmt.Println(host)
}

当然,也可以用字符串拼接,例如 |, =,  , 

type Array []string

// Scan 从数据库中读取出来
func (arr *Array) Scan(value interface{}) error {
  data, ok := value.([]byte)
  if !ok {
    return errors.New(fmt.Sprintf("解析失败: %v %T", value, value))
  }
  *arr = strings.Split(string(data), "|")
  return nil
}

// Value 存入数据库
func (arr Array) Value() (driver.Value, error) {
  return strings.Join(arr, "|"), nil
}

当然,拼接的字符串不能是输入字符串中存在的

13.3 枚举类型

枚举1.0

很多时候,我们会对一些状态进行判断,而这些状态都是有限的

例如,主机管理中,状态有 Running 运行中, OffLine 离线, Except 异常

如果存储字符串,不仅是浪费空间,每次判断还要多复制很多字符,最主要是后期维护麻烦

type Host struct {
  ID     uint
  Name   string
  Status string
}

func main() {
  host := Host{}
  if host.Status == "Running" {
    fmt.Println("在线")
  }
  if host.Status == "Except" {
    fmt.Println("异常")
  }
  if host.Status == "OffLine" {
    fmt.Println("离线")
  }
}

后来,我们知道了用常量存储这些不变的值

type Host struct {
  ID     uint
  Name   string
  Status string
}

const (
  Running = "Running"
  Except = "Except"
  OffLine = "OffLine"
) 

func main() {
  host := Host{}
  if host.Status == Running {
    fmt.Println("在线")
  }
  if host.Status == Except {
    fmt.Println("异常")
  }
  if host.Status == OffLine {
    fmt.Println("离线")
  }
}

虽然代码变多了,但是维护方便了

但是数据库中存储的依然是字符串,浪费空间这个问题并没有解决

枚举2.0

于是想到使用数字表示状态

type Host struct {
  ID     uint
  Name   string
  Status int
}

const (
  Running = 1
  Except  = 2
  OffLine = 3
)

func main() {
  host := Host{}
  if host.Status == Running {
    fmt.Println("在线")
  }
  if host.Status == Except {
    fmt.Println("异常")
  }
  if host.Status == OffLine {
    fmt.Println("离线")
  }
}

但是,如果返回数据给前端,前端接收到的状态就是数字,不过问题不大,前端反正都要搞字符映射的

因为要做颜色差异显示

但是这并不是后端偷懒的理由

于是我们想到,在json序列化的时候,根据映射转换回去

type Host struct {
  ID     uint   `json:"id"`
  Name   string `json:"name"`
  Status int    `json:"status"`
}

func (h Host) MarshalJSON() ([]byte, error) {
  var status string
  switch h.Status {
  case Running:
    status = "Running"
  case Except:
    status = "Except"
  case OffLine :
    status = "OffLine"
  }
  return json.Marshal(&struct {
    ID     uint   `json:"id"`
    Name   string `json:"name"`
    Status string `json:"status"`
  }{
    ID:     h.ID,
    Name:   h.Name,
    Status: status,
  })
}

const (
  Running = 1
  Except  = 2
  OffLine  = 3
)

func main() {
  host := Host{1, "枫枫", Running}
  data, _ := json.Marshal(host)
  fmt.Println(string(data)) // {"id":1,"name":"枫枫","status":"Running"}
}

这样写确实可以实现我们的需求,但是根本就不够通用,凡是用到枚举,都得给这个Struct实现MarshalJSON方法

枚举3.0(用这个)

于是类型别名出来了

type Status int

func (status Status) MarshalJSON() ([]byte, error) {
  var str string
  switch status {
  case Running:
    str = "Running"
  case Except:
    str = "Except"
  case OffLine:
    str = "Status"
  }
  return json.Marshal(str)
}

type Host struct {
  ID     uint   `json:"id"`
  Name   string `json:"name"`
  Status Status `json:"status"`
}

const (
  Running Status = 1
  Except  Status = 2
  OffLine Status = 3
)

func main() {
  host := Host{1, "枫枫", Running}
  data, _ := json.Marshal(host)
  fmt.Println(string(data)) // {"id":1,"name":"枫枫","status":"Running"}
}

嗯,代码简洁了不少,在使用层面已经没有问题了

在grom中使用

type Status int

func (s Status) MarshalJSON() ([]byte, error) {
  return json.Marshal(s.String())
}

func (s Status) String() string {
  var str string
  switch s {
  case Running:
    str = "Running"
  case Except:
    str = "Except"
  case OffLine:
    str = "Status"
  }
  return str
}

const (
  Running Status = 1
  OffLine Status = 2
  Except  Status = 3
)

type Host struct {
  ID     uint   `json:"id"`
  Status Status `gorm:"size:8" json:"status"`
  IP     string `json:"ip"`
}

func main() {
  //DB.AutoMigrate(&Host{})

  //DB.Create(&Host{
  //  IP:     "192.168.200.12",
  //  Status: Running,
  //})
  var host Host
  DB.Take(&host)
  fmt.Println(host)
  fmt.Printf("%#v,%T\n", host.Status, host.Status)
  data, _ := json.Marshal(host)
  fmt.Println(string(data))

}

14. gorm 事务

事务就是用户定义的一系列数据库操作,这些操作可以视为一个完成的逻辑处理工作单元,要么全部执行,要么全部不执行,是不可分割的工作单元。

很形象的一个例子,张三给李四转账100元,在程序里面,张三的余额就要-100,李四的余额就要+100
整个事件是一个整体,哪一步错了,整个事件都是失败的

gorm事务默认是开启的。为了确保数据一致性,GORM 会在事务里执行写入操作(创建、更新、删除)。

如果没有这方面的要求,您可以在初始化时禁用它,这将获得大约 30%+ 性能提升。

一般不推荐禁用

// 全局禁用
db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{
  SkipDefaultTransaction: true,
})

本节课表结构

type User struct {
  ID    uint   `json:"id"`
  Name  string `json:"name"`
  Money int    `json:"money"`
}

// InnoDB引擎才支持事务,MyISAM不支持事务
// DB.Set("gorm:table_options", "ENGINE=InnoDB").AutoMigrate(&User{})

14.1 普通事务

以张三给李四转账为例,不使用事务的后果

var zhangsan, lisi User
DB.Take(&zhangsan, "name = ?", "张三")
DB.Take(&lisi, "name = ?", "李四")
// 张三给李四转账100元
// 先给张三-100
zhangsan.Money -= 100
DB.Model(&zhangsan).Update("money", zhangsan.Money)
// 模拟失败的情况

// 再给李四+100
lisi.Money += 100
DB.Model(&lisi).Update("money", lisi.Money)

在失败的情况下,要么张三白白损失了100,要么李四凭空拿到100元

这显然是不合逻辑的,并且不合法的

那么,使用事务是怎样的

var zhangsan, lisi User
DB.Take(&zhangsan, "name = ?", "张三")
DB.Take(&lisi, "name = ?", "李四")
// 张三给李四转账100元
DB.Transaction(func(tx *gorm.DB) error {

  // 先给张三-100
  zhangsan.Money -= 100
  err := tx.Model(&zhangsan).Update("money", zhangsan.Money).Error
  if err != nil {
    fmt.Println(err)
    return err
  }

  // 再给李四+100
  lisi.Money += 100
  err = tx.Model(&lisi).Update("money", lisi.Money).Error
  if err != nil {
    fmt.Println(err)
    return err
  }
  // 提交事务
  return nil
})

使用事务之后,他们就是一体,一起成功,一起失败

14.2 手动事务

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

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

// ...

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

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

刚才的代码也可以这样实现

var zhangsan, lisi User
DB.Take(&zhangsan, "name = ?", "张三")
DB.Take(&lisi, "name = ?", "李四")

// 张三给李四转账100元
tx := DB.Begin()

// 先给张三-100
zhangsan.Money -= 100
err := tx.Model(&zhangsan).Update("money", zhangsan.Money).Error
if err != nil {
  tx.Rollback()
}

// 再给李四+100
lisi.Money += 100
err = tx.Model(&lisi).Update("money", lisi.Money).Error
if err != nil {
  tx.Rollback()
}
// 提交事务
tx.Commit()

本篇文章根据 小破站 枫枫知道 所著,也是作者很喜欢的博主哈~

创作不易,希望读者三连支持 💖
赠人玫瑰,手有余香 💖

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/536114.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

微服务-7 Docker

一、镜像、容器、仓库 容器是镜像的实例,仓库中存储着镜像。 二、镜像的操作 三、容器的操作 创建容器停止容器,查看后发现没有了(docker ps 默认只展示没有停止的) docker ps -a (可以展示运行中和停止的镜像)删除容器:(docker rm 不能删除…

新手尝试硬件买单片机还是树莓派?

新手尝试硬件买单片机还是树莓派? 新手的话,先学单片机吧,51,stm32,都可以,很多学习平台给的例子比较多,程序相对都比较简单,更贴近硬件,玩起来比较容易做出小东西&…

【简明图文教程】Node.js的下载、安装、环境配置及测试

文章目录 前言下载Node.js安装Node.js配置Node.js配置环境变量测试后言 前言 本教程适用于小白第一次从零开始进行Node.js的下载、安装、环境配置及测试。 如果你之前已经安装过了Node.js或删除掉了Node.js想重新安装,需要先参考以下博客进行处理后,再根…

HarmonyOS实战开发-图片编辑、使用 TextArea 实现多文本输入

介绍 本示例使用 TextArea 实现多文本输入,使用 ohos.app.ability.common 依赖系统的图库引用,实现在相册中获取图片,使用 ohos.multimedia.image 生成pixelMap,使用pixelMap的scale(),crop(),rotate()接口…

学习云计算HCIE选择誉天有什么优势?

誉天云计算课程优势实战性强 课程注重实践操作,通过实际案例和实验操作,让学员深入了解云计算的应用场景和实际操作技能。课程内容全面 涵盖所有云计算涉及的IT基础知识、服务器、存储、网络等方面的基础知识,开源操作系统Linux,开…

HarmonyOS实战开发-拼图、如何实现获取图片,以及图片裁剪分割的功能。

介绍 该示例通过ohos.multimedia.image和ohos.multimedia.mediaLibrary接口实现获取图片,以及图片裁剪分割的功能。 效果预览 使用说明: 使用预置相机拍照后启动应用,应用首页会读取设备内的图片文件并展示获取到的第一个图片,…

【muzzik 分享】3D模型平面切割

# 前言 一年一度的征稿到了,倒腾点存货,3D平面切割通常用于一些解压游戏里,例如水果忍者,切菜这些,今天我就给大家讲讲怎么实现3D切割以及其原理,帮助大家更理解3D中的 Mesh(网格),以及UV贴图和…

机器学习和深度学习--李宏毅(笔记与个人理解)Day11-12

Day11 when gradient is small…… 怎么知道是局部小 还是鞍点? using Math 这里巧妙的说明了hessan矩阵可以决定一个二次函数的凹凸性 也就是 θ \theta θ 是min 还是max,最后那个有些有些 哈 是一个saddle; 然后这里只要看hessan矩阵是不…

Element-UI 下拉框单选转多选回显不清空绑定的值

需求 根据radio切换来更改下拉框是否多选 原因 单选和多选这两个 input 看上去没差别&#xff08;自身和层级都一致&#xff09;&#xff0c;vue出于提高性能&#xff0c;所以 vue 给复用了 解决方案 <template><section><el-radio-group v-model"radi…

风储微网虚拟惯性控制系统simulink建模与仿真

目录 1.课题概述 2.系统仿真结果 3.核心程序与模型 4.系统原理简介 5.完整工程文件 1.课题概述 风储微网虚拟惯性控制系统simulink建模与仿真。风储微网虚拟惯性控制系统是一种模仿传统同步发电机惯性特性的控制策略&#xff0c;它通过集成风力发电系统、储能系统和其他分…

聚丙烯PP它的化学特性是什么? UV胶水能够粘接聚丙烯PP吗?

聚丙烯PP它的化学特性是什么? UV胶水能够粘接聚丙烯PP吗&#xff1f; 聚丙烯&#xff08;Polypropylene&#xff0c;简称PP&#xff09;是一种热塑性聚合物&#xff0c;属于聚烯烃类塑料之一。以下是聚丙烯的一些化学特性&#xff1a; 1. 分子结构&#xff1a; 聚丙烯是由丙烯…

再说vue响应式数据

请说一下你对响应式数据的理解 如何实现响应式数据据 对象 vue2 响应式核心代码 数组 vue2 处理缺陷Vue3则采用 proxy - vue3 响应式核心代码 请说一下你对响应式数据的理解 如何实现响应式数据据 数组和对象类型当值变化时如何劫持到。 对象 对象内部通过defineReactive方…

如何将普通maven项目转为maven-web项目

文件-项目结构&#xff08;File-->Project Structure &#xff09; 模块-->learn&#xff08;moudle-->learn&#xff09; 选中需要添加web的moudle&#xff0c;点击加号&#xff0c;我得是learn&#xff0c;单击选中后进行下如图操作&#xff1a; 编辑路径 结果如下…

Centos7 k8s 集群 - Rook Ceph 安装

环境准备 基础环境 系统名称操作系统CPU内存硬盘Kubernete 版本Docker版本IPmasterCentos74c4gsdb 20G1.17.023.0.1192.168.1.128node01Centos74c4gsdb 20G1.17.023.0.1192.168.1.129node02Centos74c4gsdb 20G1.17.023.0.1192.168.1.130node03Centos74c4gsdb 20G1.17.023.0.1…

OpenHarmony4.0分布式任务调度浅析

1 概述 OpenHarmony 分布式任务调度是一种基于分布式软总线、分布式数据管理、分布式 Profile 等技术特性的任务调度方式。它通过构建一种统一的分布式服务管理机制&#xff0c;包括服务发现、同步、注册和调用等环节&#xff0c;实现了对跨设备的应用进行远程启动、远程调用、…

3d怎么按路径制作模型---模大狮模型网

在3D建模中&#xff0c;按路径制作模型是一种常见的技术&#xff0c;特别适用于创建曲线、管道、绳索等线性形状的物体。虽然这项技术可能对初学者来说有些复杂&#xff0c;但通过一步步的指导和实践&#xff0c;你将能够掌握它。本文将详细介绍按路径制作模型的步骤&#xff0…

OpenDDS-3.27构建与用法

一、OpenDDS-3.27构建 ./configure To enable Java bindings, use ./configure --java make 二、运行Messenger Example&#xff1a; source setenv.sh For the C example&#xff1a;cd DevGuideExamples/DCPS/Messenger For the Java example&#xff1a;cd java/tests/mes…

【JVM】JVM堆占用情况分析(频繁创建的对象、内存泄露等问题)、jmap+jhat、jvisualvm工具使用

文章目录 一. 相关命令1. 查看进程堆内存整体使用情况&#xff1a;OOM的可能2. 统计类的对象数量以及内存占用&#xff1a;定位内存泄漏 二. 分析内存占用1. 使用 jhat 排查对象堆占用情况1.1. 排查步骤1.2. 具体分析例子a. 分析频繁创建对象导致的OOM 1.3. OQL查看某一个对象的…

Python 基于 OpenCV 视觉图像处理实战 之 OpenCV 简单视频处理实战案例 之十 简单视频浮雕画效果

Python 基于 OpenCV 视觉图像处理实战 之 OpenCV 简单视频处理实战案例 之十 简单视频浮雕画效果 目录 Python 基于 OpenCV 视觉图像处理实战 之 OpenCV 简单视频处理实战案例 之十 简单视频浮雕画效果 一、简单介绍 二、简单视频浮雕画效果实现原理 三、简单视频浮雕画效果…

关于MCU产品开发参数存储的几种方案

关于MCU产品开发参数存储的几种方案 Chapter1 关于MCU产品开发参数存储的几种方案Chapter2 单片机参数处理[保存与读取]Chapter3 嵌入式设备参数存储技巧Chapter4 STM32硬件I2C的一点心得(AT24C32C和AT24C64C) Chapter1 关于MCU产品开发参数存储的几种方案 原文链接 在工作中…