Go gorm库(详细版)

这篇具有很好参考价值的文章主要介绍了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

Go gorm库(详细版),Golang,golang,gorm,mysql

Go gorm库(详细版),Golang,golang,gorm,mysql

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() {

}

高级设置

Go gorm库(详细版),Golang,golang,gorm,mysql

是在open的地方设置

Go gorm库(详细版),Golang,golang,gorm,mysql

gorm 的命名策略

Go gorm库(详细版),Golang,golang,gorm,mysql

Go gorm库(详细版),Golang,golang,gorm,mysql

创建表

Go gorm库(详细版),Golang,golang,gorm,mysql

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

Go gorm库(详细版),Golang,golang,gorm,mysql

日志显示

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

Go gorm库(详细版),Golang,golang,gorm,mysql

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

Go gorm库(详细版),Golang,golang,gorm,mysql

推荐:

Go gorm库(详细版),Golang,golang,gorm,mysql

04. 模型定义

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

定义一张表

Go gorm库(详细版),Golang,golang,gorm,mysql

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

自动生成表结构

Go gorm库(详细版),Golang,golang,gorm,mysql

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

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

新增Email和改Name为Name1

Go gorm库(详细版),Golang,golang,gorm,mysql

Go gorm库(详细版),Golang,golang,gorm,mysql

修改表字段大小

有两种方式

Go gorm库(详细版),Golang,golang,gorm,mysql

下面那种样例

Go gorm库(详细版),Golang,golang,gorm,mysql

Go gorm库(详细版),Golang,golang,gorm,mysql

字段标签

Go gorm库(详细版),Golang,golang,gorm,mysql

Go gorm库(详细版),Golang,golang,gorm,mysql

05. 单表查询

5.1 表结构

Go gorm库(详细版),Golang,golang,gorm,mysql

5.2 添加单条记录

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

Go gorm库(详细版),Golang,golang,gorm,mysql

有两条是因为点了两下哈

Go gorm库(详细版),Golang,golang,gorm,mysql

如果此时

Go gorm库(详细版),Golang,golang,gorm,mysql

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

Go gorm库(详细版),Golang,golang,gorm,mysql

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

还可以打印s1看看

Go gorm库(详细版),Golang,golang,gorm,mysql

5.3 批量插入

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

Go gorm库(详细版),Golang,golang,gorm,mysql

Go gorm库(详细版),Golang,golang,gorm,mysql

Go gorm库(详细版),Golang,golang,gorm,mysql

5.4 单条数据查询

会查询第一个数据

Go gorm库(详细版),Golang,golang,gorm,mysql

Go gorm库(详细版),Golang,golang,gorm,mysql

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

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

5.5 根据主键查询

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

Go gorm库(详细版),Golang,golang,gorm,mysql

Go gorm库(详细版),Golang,golang,gorm,mysql

5.6 根据结构体进行查询

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

Go gorm库(详细版),Golang,golang,gorm,mysql

5.7 获取查询的结构

Go gorm库(详细版),Golang,golang,gorm,mysql

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

Go gorm库(详细版),Golang,golang,gorm,mysql

Go gorm库(详细版),Golang,golang,gorm,mysql

5.9 根据主键列表去查询

Go gorm库(详细版),Golang,golang,gorm,mysql

根据其他条件就可以

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

06. 更新数据

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

6.1 Save 保存所有字段

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

Go gorm库(详细版),Golang,golang,gorm,mysql

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

Select更新指定字段Go gorm库(详细版),Golang,golang,gorm,mysql

6.2 Update批量更新

Go gorm库(详细版),Golang,golang,gorm,mysql

或者Model

Go gorm库(详细版),Golang,golang,gorm,mysql

6.3 Updates更新

可以传结构体

Go gorm库(详细版),Golang,golang,gorm,mysql

也可以传map

Go gorm库(详细版),Golang,golang,gorm,mysql

07. 删除数据

Go gorm库(详细版),Golang,golang,gorm,mysql

08. 添加钩子函数(HOOK)

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

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

Go gorm库(详细版),Golang,golang,gorm,mysql

在实现BeforeCreate之后

Go gorm库(详细版),Golang,golang,gorm,mysql

插入这个

Go gorm库(详细版),Golang,golang,gorm,mysql

Go gorm库(详细版),Golang,golang,gorm,mysql

09. Gorm高级查询

9.01 Where查询

我们重构一些数据

Go gorm库(详细版),Golang,golang,gorm,mysql

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

Go gorm库(详细版),Golang,golang,gorm,mysql

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

Go gorm库(详细版),Golang,golang,gorm,mysql

Go gorm库(详细版),Golang,golang,gorm,mysqlGo gorm库(详细版),Golang,golang,gorm,mysql

9.02 Select 选择字段

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

Go gorm库(详细版),Golang,golang,gorm,mysql

Go gorm库(详细版),Golang,golang,gorm,mysql

Go gorm库(详细版),Golang,golang,gorm,mysql

9.03 排序

Go gorm库(详细版),Golang,golang,gorm,mysql

9.04 分页查询

先看纯sql的分页查询

Go gorm库(详细版),Golang,golang,gorm,mysql

Go gorm库(详细版),Golang,golang,gorm,mysql

9.05 去重

 先看sql去重

Go gorm库(详细版),Golang,golang,gorm,mysql

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

9.06 分组查询

Go gorm库(详细版),Golang,golang,gorm,mysql

Go gorm库(详细版),Golang,golang,gorm,mysql

Go gorm库(详细版),Golang,golang,gorm,mysql

Go gorm库(详细版),Golang,golang,gorm,mysql

sql拼接名字

Go gorm库(详细版),Golang,golang,gorm,mysql

gorm写法

Go gorm库(详细版),Golang,golang,gorm,mysql

9.07 gorm执行原生sql

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

9.08 子查询

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

Go gorm库(详细版),Golang,golang,gorm,mysql

9.09 命名参数

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

Go gorm库(详细版),Golang,golang,gorm,mysql

Go gorm库(详细版),Golang,golang,gorm,mysql

9.10 从Find到Map

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

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

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

Go gorm库(详细版),Golang,golang,gorm,mysql

emmmmmm好像没什么区别

9.11 查询引用Scope

Go gorm库(详细版),Golang,golang,gorm,mysql

10. 一对多关系

Go gorm库(详细版),Golang,golang,gorm,mysql

10.1 表结构建立

Go gorm库(详细版),Golang,golang,gorm,mysql

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

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

重写外键关联

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

Go gorm库(详细版),Golang,golang,gorm,mysql

10.2 添加数据

Go gorm库(详细版),Golang,golang,gorm,mysql

创建用户的时候创建文章

Go gorm库(详细版),Golang,golang,gorm,mysql

Go gorm库(详细版),Golang,golang,gorm,mysql

Go gorm库(详细版),Golang,golang,gorm,mysql

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

Go gorm库(详细版),Golang,golang,gorm,mysql

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

Go gorm库(详细版),Golang,golang,gorm,mysql

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

Go gorm库(详细版),Golang,golang,gorm,mysql

外键添加

其中Association和Append的方式更常用

Go gorm库(详细版),Golang,golang,gorm,mysql

Go gorm库(详细版),Golang,golang,gorm,mysql

10.3 查询数据

Go gorm库(详细版),Golang,golang,gorm,mysql

预加载

Go gorm库(详细版),Golang,golang,gorm,mysql

嵌套预加载

Go gorm库(详细版),Golang,golang,gorm,mysql

带条件的预加载

Go gorm库(详细版),Golang,golang,gorm,mysql

自定义预加载

Go gorm库(详细版),Golang,golang,gorm,mysql

10.4 删除数据

级联删除

Go gorm库(详细版),Golang,golang,gorm,mysql

清除外键关系

Go gorm库(详细版),Golang,golang,gorm,mysql

11. 一对一关系

Go gorm库(详细版),Golang,golang,gorm,mysql

表结构搭建

Go gorm库(详细版),Golang,golang,gorm,mysql

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

添加记录

Go gorm库(详细版),Golang,golang,gorm,mysql

查询

Go gorm库(详细版),Golang,golang,gorm,mysql

删除

删除和一对多是一样的

Go gorm库(详细版),Golang,golang,gorm,mysql

12. 多对多关系

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

12.1 表结构搭建

Go gorm库(详细版),Golang,golang,gorm,mysql

12.2 添加

添加文章,并创建标签

Go gorm库(详细版),Golang,golang,gorm,mysql

查看表

Go gorm库(详细版),Golang,golang,gorm,mysql

Go gorm库(详细版),Golang,golang,gorm,mysql

Go gorm库(详细版),Golang,golang,gorm,mysql

创建文章,添加已有标签

Go gorm库(详细版),Golang,golang,gorm,mysql

12.3 查询

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

Go gorm库(详细版),Golang,golang,gorm,mysql

查询标签,显示文章列表

Go gorm库(详细版),Golang,golang,gorm,mysql

12.4 更新

之前的方式

Go gorm库(详细版),Golang,golang,gorm,mysql

Go gorm库(详细版),Golang,golang,gorm,mysql

gorm提供的方法

让tag1替换为tag2

Go gorm库(详细版),Golang,golang,gorm,mysql

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

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

12.5.1 表结构及生成

Go gorm库(详细版),Golang,golang,gorm,mysql

注意`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()

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

创作不易,希望读者三连支持 💖
赠人玫瑰,手有余香 💖
文章来源地址https://www.toymoban.com/news/detail-852710.html

到了这里,关于Go gorm库(详细版)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 开源 Golang 微服务入门三:ORM 框架 GORM

    前两篇笔记分别介绍了 Golang 微服务 HTTP 框架 Hertz 和 Golang 微服务 RPC 框架 Kitex,本文将要介绍面向ORM(持久层)框架 GORM。 官方文档 GORM 是面向 Golang 语言的一种 ORM(持久层)框架,支持多种数据库的接入,例如 MySQL,PostgreSQL,SQLite,SQL Server,Clickhouse。此框架的特点,弱化了开

    2024年02月14日
    浏览(27)
  • 【Golang】VsCode下开发Go语言的环境配置(超详细图文详解)

    📓推荐网站(不断完善中):个人博客 📌个人主页:个人主页 👉相关专栏:CSDN专栏、个人专栏 🏝立志赚钱,干活想躺,瞎分享的摸鱼工程师一枚 ​ 话说在前,Go语言的编码方式是 UTF-8 ,理论上你直接使用文本进行编辑也是可以的,当然为了提升我们的开发效率我们还是需

    2024年02月07日
    浏览(70)
  • golang gorm通过泛型实现通用单表增删改

    无废话,直接上代码 调用方式 给出调用方式案例 可以依照代码,写出其它灵活的使用方式 结束

    2024年02月05日
    浏览(26)
  • Golang+Gorm库使用踩坑——未标识primarykey导致创建后无法返回修改

    做毕设ing,基本的增删改查。 这里是一个需要增的地方,代码如下: 我在外层调用时候,是需要返回新增记录的ID。但是无法符合预期。通过打印发现,我这里返回的id就是0。 翻阅之前写的一个正确样例,对比发现 区别在于,我这里没有去指定主键。 根据官方文档,其de

    2024年01月19日
    浏览(31)
  • 前后端分离项目(gin+gorm+vue3)腾讯云部署详细教程(Xshell安装及使用,go,mysql,nginx安装及配置)

    1. 购买服务器 进入腾讯云官网点击最新活动,进入云+校园专区,我购买的是第一个 购买完成后,进入控制台,云产品选择轻量应用服务器,即可查看到自己的服务器,记住自己的公网IP 2. 重置密码 点击进入服务器详情,初次使用需重置密码 点击重置密码按提示操作即可,

    2024年02月06日
    浏览(54)
  • [Golang实战] gorm中使用Raw()和 Exec() 两种方式操作sql原生语句的特点和区别

    当我在gorm中使用原生sql操作数据库时,时常用raw() 和 exec() ,有时候经常遇到数据插不进去或者 数据帮i当不到结构体,原来是 这两个方法有不同的用处和特点 个人理解: 需要查询数据 映射到结构体时使用 官方理解: DB.Raw() 方法用于执行原始 SQL 查询语句或可执行的命令。

    2024年02月13日
    浏览(42)
  • Go gorm库(详细版)

    目录 01. 什么是ORM 02. 环境搭建 03. 连接数据库 高级设置 gorm 的命名策略 创建表 日志显示 04. 模型定义 定义一张表 自动生成表结构 修改表字段大小 字段标签 05. 单表查询 5.1 表结构 5.2 添加单条记录 5.3 批量插入 5.4 单条数据查询 5.5 根据主键查询 5.6 根据结构体进行查询 5.7

    2024年04月15日
    浏览(18)
  • go gorm 操作MySQL初探

    安装 实例 参考 https://duoke360.com/tutorial/gorm/query-recored

    2024年02月12日
    浏览(30)
  • 【Golang】Golang进阶系列教程--Go 语言切片是如何扩容的?

    在 Go 语言中,有一个很常用的数据结构,那就是切片(Slice)。 切片是一个拥有相同类型元素的可变长度的序列,它是基于数组类型做的一层封装。它非常灵活,支持自动扩容。 切片是一种引用类型,它有三个属性:指针,长度和容量。 底层源码定义如下: 指针: 指向

    2024年02月14日
    浏览(43)
  • 【Golang】Golang进阶系列教程--Go 语言 map 如何顺序读取?

    Go 语言中的 map 是一种非常强大的数据结构,它允许我们快速地存储和检索键值对。 然而,当我们遍历 map 时,会有一个有趣的现象,那就是输出的键值对顺序是不确定的。 先看一段代码示例: 当我们多执行几次这段代码时,就会发现,输出的顺序是不同的。 首先,Go 语言

    2024年02月14日
    浏览(47)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包