GORM 入门笔记(五)简单的增删改查
本篇使用以下示例用表
1 | type User struct { |
创建
单独创建
使用 Create() 方法即可
1 | GLOBAL_DB.Create(&User{Name: "张三", Age: 18}) |
而如何知道有没有创建成功呢?
观察可以发现,这个方法会返回 DB 对象

1 | // DB GORM DB definition |
而 DB 有 Error 和 RowsAffected 这两个属性,就可以很方便地得到结果
1 | func CreateUser() { |

可以看见并没有报错
那么如果报错是怎样的呢?可以添加一个 NOT NULL 的字段,然后不提供值试一下
1 | type User struct { |

可以看见报错信息
创建时指定字段
在 Create 之前使用 Select 方法可以只会传递被选中的字段值(没被选中的如果有默认值就会被赋成默认)
下面的例子只会创建 Name 而不会有 Age
1 | GLOBAL_DB.Select("Name").Create(&User{Name: "张三", Age: 18}) |

创建时跳过字段
类似地,还可以使用 Omit 跳过字段,下面的例子只会创建 Age 而不会有 Name
1 | GLOBAL_DB.Omit("Name").Create(&User{Name: "张三", Age: 18}) |

批量创建
可以使用切片来批量创建
1 | GLOBAL_DB.Create(&[]User{ |

查询
我感觉 GROM 的查询玩的太花了,方法(字面义)很多,这里只讲一部分
查询主键排序后的第一条
使用 First() 方法
查询时有两种方式来接收返回值,分别是 Map 和结构体
在查询的时候必须在语句中体现原始模型,即使是使用 Table("users") 指定表名也不行,所以如果要使用 Map 接收,必须使用 Model(&User{}) 手动指定模型
1 | var result = make(map[string]interface{}) |

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

查询主键排序后的最后一条
类似地,还有 Last() 方法,不同之处只是拿最后一条
1 | var result User |

查询第一条(不排序)
再类似地,还有 take() 方法
1 | var result User |
这东西在生成 SQL 时不会添加按主键排序的字句,不过在这里结果肯定是一样的

查询多条记录
很简单,把结构体变成切片,然后使用 Find() 或是 Scan()

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

在字符串里可以像 SQL 一样使用 AND 和 OR 关键字
1 | var result []User |

当然了,如果只想查一个的话,使用 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) |

为什么 NULL 不会被选中?我想因为 GORM 是转换成 SQL 去查询的,而 SQL 去查询 MySQL 的时候,NULL 就是不会被选中
内联条件
在查询动作方法中直接加上 Where() 中的内容
1 | GLOBAL_DB.Find(&result, "name = ?", "张三") |
这个应该没什么好说的
主键检索
如果主键是数字类型,可以使用简化的内联条件
例如下面查找主键为 4 的记录
1 | var result User |

类似地,还有下面的用法
1 | var result []User |

如果你的主键是字符串(例如 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 | GLOBAL_DB.Select("name", "age").Find(&result) |

这时其他的字段都没有获取到,全是零值
智能选择字段
使用你想要的字段定义结构体,然后用它来收集结果
因为无法得知原始模型,所以需要加上 Model() 方法
1 | type SampleUser struct { |

这样就很简洁了
Limit & Offset
这个很简单,直接用,不懂的去看文档吧
排序
这个也看文档吧
更新
更新的话只是把上面查找的方法全变成更新的方法
等于就是选中集合,上面最后是查找,但是现在是更新
Update()
只更新你选择的字段
1 | GLOBAL_DB.Model(&User{}).Where("name = ?", "张三").Update("age", 19) |

Save()
将查询的结果先暂存,更改后再提交
无论如何都会更新,包括零值
1 | var result User |

可以更改多行
1 | var result []User |

Updates()
更新所有字段,有两种形式
Struct
若使用结构体,零值不参与更新
1 | var result User |
本来是可以同时更新两个列的,但是因为 0 是整型的零值,所以不会更新 Age

Map
但是如果使用 Map,即使是零值也会更新
1 | var result User |

如果要同时更新多行,只需将结果集变成切片
1 | var result []User |

删除
删除只有 Delete() 一个方法,默认是软删除(仅写入 delete_at 字段)
1 | GLOBAL_DB.Where("name = ?", "").Delete(&User{}) |
删除所有名字为空的记录

如果想要硬删除,可以加上 Unscoped() 方法
1 | GLOBAL_DB.Unscoped().Where("name = ?", "").Delete(&User{}) |

原生SQL
若要使用原生 SQL 查询 ,末尾只能为 Scan()
1 | var result User |

错误检查
对于可能产生错误的语句,应当始终进行错误检查
例如按照不存在的主键进行查询
1 | dbRes := GLOBAL_DB.First(&result, 6) |

它的结果会是空,然后返回 record not found 错误
业务中会将拿到的错误与 GORM 中预定义的错误进行比较,就像下面这样
1 | errors.Is(dbRes.Error, gorm.ErrRecordNotFound) |
