本篇使用以下示例用表

1
2
3
4
5
6
7
type User struct {
gorm.Model
Name string
Age uint8
}

GLOBAL_DB.AutoMigrate(&User{})

创建

官方文档:https://gorm.io/zh_CN/docs/create.html

单独创建

使用 Create() 方法即可

1
GLOBAL_DB.Create(&User{Name: "张三", Age: 18})

而如何知道有没有创建成功呢?

观察可以发现,这个方法会返回 DB 对象

image-20221001204202540

1
2
3
4
5
6
7
8
// DB GORM DB definition
type DB struct {
*Config
Error error
RowsAffected int64
Statement *Statement
clone int
}

而 DB 有 ErrorRowsAffected 这两个属性,就可以很方便地得到结果

1
2
3
4
func CreateUser() {
dbres := GLOBAL_DB.Create(&User{Name: "张三", Age: 18})
fmt.Println(dbres.Error, dbres.RowsAffected)
}

image-20221001205125879

可以看见并没有报错


那么如果报错是怎样的呢?可以添加一个 NOT NULL 的字段,然后不提供值试一下

1
2
3
4
5
6
type User struct {
gorm.Model
T *string `gorm:"not null"`
Name string
Age uint8
}

image-20221001205558185

可以看见报错信息


创建时指定字段

Create 之前使用 Select 方法可以只会传递被选中的字段值(没被选中的如果有默认值就会被赋成默认)

下面的例子只会创建 Name 而不会有 Age

1
GLOBAL_DB.Select("Name").Create(&User{Name: "张三", Age: 18})

image-20221001210649070


创建时跳过字段

类似地,还可以使用 Omit 跳过字段,下面的例子只会创建 Age 而不会有 Name

1
GLOBAL_DB.Omit("Name").Create(&User{Name: "张三", Age: 18})

image-20221001210845144


批量创建

可以使用切片来批量创建

1
2
3
4
5
GLOBAL_DB.Create(&[]User{
{Name: "张三", Age: 18},
{Name: "李四", Age: 20},
{Name: "王五", Age: 22},
})

image-20221001211250474


查询

官方文档:https://gorm.io/zh_CN/docs/query.html

我感觉 GROM 的查询玩的太花了,方法(字面义)很多,这里只讲一部分

查询主键排序后的第一条

使用 First() 方法

查询时有两种方式来接收返回值,分别是 Map 和结构体

在查询的时候必须在语句中体现原始模型,即使是使用 Table("users") 指定表名也不行,所以如果要使用 Map 接收,必须使用 Model(&User{}) 手动指定模型

1
2
var result = make(map[string]interface{})
GLOBAL_DB.Model(&User{}).First(&result)

image-20221001215252615

另外一种是使用模型的结构体,因为结构体本身已经能体现原始模型,所以无需使用 Model 方法

1
2
var result User
GLOBAL_DB.First(&result)

image-20221001215535453


查询主键排序后的最后一条

类似地,还有 Last() 方法,不同之处只是拿最后一条

1
2
var result User
GLOBAL_DB.Last(&result)

image-20221001220039085


查询第一条(不排序)

再类似地,还有 take() 方法

1
2
var result User
GLOBAL_DB.Take(&result)

这东西在生成 SQL 时不会添加按主键排序的字句,不过在这里结果肯定是一样的

image-20221001215639688


查询多条记录

很简单,把结构体变成切片,然后使用 Find() 或是 Scan()

image-20221001233120631


使用检索条件

使用 Where() 给出条件

String 条件

传入字符串给 Where() 以作为条件,把可以把这东西直接当做 SQL 中的 WHERE 字句,什么 AND NOT OR LIKE 都可以用,而且可以使用 ? 作为占位符,格式化字符串

1
2
var result []User
GLOBAL_DB.Where("name = ?", "张三").Find(&result)

image-20221001225135002

在字符串里可以像 SQL 一样使用 ANDOR 关键字

1
2
var result []User
GLOBAL_DB.Where("name = ? OR age = 22", "张三").Find(&result)

image-20221001225619329

当然了,如果只想查一个的话,使用 First() 就行


Struct & Map 条件

这个不常用,建议去官方文档


Or()Not()

上面的

1
GLOBAL_DB.Where("name = ? OR age = 22", "张三").Find(&result)

还可以这样写

1
GLOBAL_DB.Where("name = ?", "张三").Or("age = 22").Find(&result)

等于说是抽离出来了,结果是一样的

Not() 可以这样用

1
GLOBAL_DB.Not("name = ?", "张三").Find(&result)

image-20221001232206846

为什么 NULL 不会被选中?我想因为 GORM 是转换成 SQL 去查询的,而 SQL 去查询 MySQL 的时候,NULL 就是不会被选中


内联条件

在查询动作方法中直接加上 Where() 中的内容

1
GLOBAL_DB.Find(&result, "name = ?", "张三")

这个应该没什么好说的


主键检索

如果主键是数字类型,可以使用简化的内联条件

例如下面查找主键为 4 的记录

1
2
3
4
var result User
GLOBAL_DB.First(&result, 4)
// 或者是
GLOBAL_DB.First(&result, "4")

image-20221001221817626

类似地,还有下面的用法

1
2
var result []User
GLOBAL_DB.Find(&result, []int{1, 2, 3})

image-20221001223217616

如果你的主键是字符串(例如 UUID),就必须写成正式的内联查询或使用 Where()

1
GLOBAL_DB.First(&result, "id = ?", "1b74413f-f3b8-409f-ac47-e8c062e3472a")

而不能这么写

1
GLOBAL_DB.First(&result,"1b74413f-f3b8-409f-ac47-e8c062e3472a")

选择特定字段

使用 Select() 选择你想要的字段

1
2
3
GLOBAL_DB.Select("name", "age").Find(&result)
// 或是
GLOBAL_DB.Select([]string{"name", "age"}).Find(&result)

image-20221001234110193

这时其他的字段都没有获取到,全是零值


智能选择字段

使用你想要的字段定义结构体,然后用它来收集结果

因为无法得知原始模型,所以需要加上 Model() 方法

1
2
3
4
5
6
7
8
9
10
11
type SampleUser struct {
Name string
Age uint8
}

func FindUser() {
var result []SampleUser
GLOBAL_DB.Model(&User{}).Find(&result)

fmt.Println(result)
}

image-20221001234814825

这样就很简洁了


Limit & Offset

这个很简单,直接用,不懂的去看文档


排序

这个也看文档吧


更新

官方文档:https://gorm.io/zh_CN/docs/update.html

更新的话只是把上面查找的方法全变成更新的方法

等于就是选中集合,上面最后是查找,但是现在是更新

Update()

只更新你选择的字段

1
GLOBAL_DB.Model(&User{}).Where("name = ?", "张三").Update("age", 19)

image-20221002104439580


Save()

将查询的结果先暂存,更改后再提交

无论如何都会更新,包括零值

1
2
3
4
var result User
GLOBAL_DB.First(&result)
result.Age = 20
GLOBAL_DB.Save(&result)

image-20221002104814617

可以更改多行

1
2
3
4
5
6
var result []User
GLOBAL_DB.Find(&result)
for k := range result {
result[k].Age = 20
}
GLOBAL_DB.Save(&result)

image-20221002105014532


Updates()

更新所有字段,有两种形式

Struct

若使用结构体,零值不参与更新

1
2
var result User
GLOBAL_DB.First(&result).Updates(User{Name: "李四", Age: 0})

本来是可以同时更新两个列的,但是因为 0 是整型的零值,所以不会更新 Age

image-20221002105913748

Map

但是如果使用 Map,即使是零值也会更新

1
2
var result User
GLOBAL_DB.First(&result).Updates(map[string]interface{}{"name": "", "age": 0})

image-20221002110215104

如果要同时更新多行,只需将结果集变成切片

1
2
var result []User
GLOBAL_DB.Find(&result, []int{1, 2, 3}).Updates(map[string]interface{}{"name": "", "age": 0})

image-20221002110430962


删除

官方文档:https://gorm.io/zh_CN/docs/delete.html

删除只有 Delete() 一个方法,默认是软删除(仅写入 delete_at 字段)

1
GLOBAL_DB.Where("name = ?", "").Delete(&User{})

删除所有名字为空的记录

image-20221002111231247

如果想要硬删除,可以加上 Unscoped() 方法

1
GLOBAL_DB.Unscoped().Where("name = ?", "").Delete(&User{})

image-20221002111423619


原生SQL

官方文档:https://gorm.io/zh_CN/docs/sql_builder.html

若要使用原生 SQL 查询 ,末尾只能为 Scan()

1
2
var result User
GLOBAL_DB.Raw("select * from users where id = ?", 4).Scan(&result)

image-20221002112411160


错误检查

对于可能产生错误的语句,应当始终进行错误检查

例如按照不存在的主键进行查询

1
dbRes := GLOBAL_DB.First(&result, 6)

image-20221001224153168

它的结果会是空,然后返回 record not found 错误

业务中会将拿到的错误与 GORM 中预定义的错误进行比较,就像下面这样

1
errors.Is(dbRes.Error, gorm.ErrRecordNotFound)

image-20221001224537348