This repository has been archived on 2026-05-24. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
all-in-kingsoft/hhs/GORM/03-CRUD 操作.md
T

235 lines
6.8 KiB
Markdown
Raw Normal View History

2026-04-28 20:02:42 +08:00
---
tags: [GORM, Go, ORM, CRUD, 增删改查, First, Find, Create, Update, Delete]
create time: 2026-04-28 00:00
---
# CRUD 操作
## 概述
CRUD 是最基础的数据库操作。GORM 提供了语义清晰的五个核心查询方法(First / Take / Find / Get / Last)和多种更新删除 API。掌握它们就能覆盖日常 80% 的数据交互需求。
## 五大查询方法
```mermaid
flowchart LR
A[db.Model(&User{})] --> B{First / Take / Last}
A --> C[Find - 批量查询]
A --> D[Get - 封装版 First]
B --> E["单条记录<br/>返回 *User"]
C --> F["多条记录切片<br/>返回 []User"]
D --> E
style A fill:#4FC08D,color:#fff
style B fill:#3B82F6,color:#fff
style C fill:#8B5CF6,color:#fff
style E fill:#10B981,color:#fff
style F fill:#F59E0B,color:#fff
style D fill:#EC4899,color:#fff
```
### 方法对比
| 方法 | 返回类型 | 行为 | 是否需要 Where |
|------|----------|------|----------------|
| `First` | 单条指针 | 按主键排序取第一条,**必须有条件** | ✅ 必须 |
| `Take` | 单条指针 | 随机取一条(ORDER BY 随机),**必须有条件** | ✅ 必须 |
| `Last` | 单条指针 | 按主键倒序取最后一条 | ✅ 必须 |
| `Find` | 切片 | 查询所有匹配行 | ❌ 可选 |
| `Get` | 单条指针 | `First` 的封装,自动处理空结果 | 同 First |
### First vs Take 的区别
```go
// First: 按主键升序取第一条(确定性)
db.First(&user) // SELECT * FROM users ORDER BY primary_key LIMIT 1; WHERE ?
// Take: 无序取任意一条(用于随机抽样)
db.Take(&user) // SELECT * FROM users LIMIT 1;
```
### 实际用法示例
```go
// 1. First - 根据主键查找
var user User
db.First(&user, 10) // WHERE id = 10
db.First(&user, "name = ?", "john") // WHERE name = 'john'
// 2. Take - 随机取一条(抽奖场景常用)
var product Product
db.Take(&product) // 任意一条商品
// 3. Find - 批量查询
var users []User
db.Where("age > ?", 18).Find(&users) // WHERE age > 18
// 4. Get - 安全版的 First
err := db.First(&user).Error
if errors.Is(err, gorm.ErrRecordNotFound) {
// 记录不存在
}
```
> [!danger] 常见误区
> `Find` 不带 `Where` 会查询全表!数据量大时会导致 OOM。始终记得加过滤条件,或用 `Limit` 兜底。
## 创建(Create)
```go
// 单条插入
user := User{Name: "Alice", Age: 30}
result := db.Create(&user)
// result.RowsAffected — 影响行数
// result.Error — 错误
// 批量插入(至少两条才生效批量优化)
users := []User{
{Name: "Bob", Age: 25},
{Name: "Charlie", Age: 35},
}
db.Create(&users) // INSERT INTO users ... VALUES (...), (...), (...)
```
> [!tip] 批量插入上限
> GORM 内部会将批量 insert 拆分为每批约 256 条,避免单次 SQL 过大。如需调整,可通过 `db.Session(&gorm.Session{FullSaveRecords: true})` 控制行为。
### 插入后获取自增 ID
```go
db.Create(&user)
fmt.Println(user.ID) // 插入后 ID 自动回填到结构体
```
## 更新(Update)
GORM 提供多种更新粒度,从单个字段到全量替换:
### 方法速查表
| 方法 | 签名 | 行为 |
|------|------|------|
| `Update` | `(col string, value any)` | 更新单个字段 |
| `Updates` | `(value Model | map[string]any)` | 更新一个或多个字段 |
| `Save` | `(value Model)` | 全量更新所有字段 |
| `UpdateColumn` | `(col string, value any)` | 同 Update 但不触发 Hooks |
| `UpdateColumns` | `(value Model)` | 同 Updates 但不触发 Hooks |
### 详细示例
```go
// 1. Update - 单字段
db.Model(&user).Update("name", "Bob")
// UPDATE users SET name='Bob', updated_at=... WHERE id=...
// 2. Updates - 多字段(map 方式,零值也会被更新)
db.Model(&user).Updates(map[string]any{
"name": "Alice",
"age": 0, // 注意:0 也会被写入
"role": "admin",
})
// 3. Updates - 多字段(struct 方式,只更新非零值字段!)
db.Model(&user).Updates(User{Name: "Alice", Role: "admin"})
// 只有 Name 和 Role 被更新,Age 保持不变
// 4. Save - 全量覆盖(全部字段都写回去)
db.Save(&user)
// UPDATE users SET name='Alice', age=30, role='admin', ... WHERE id=...
```
> [!warning] Zero Values 陷阱
> `Updates` 用 struct 传入时,**零值字段("" 0 false)不会被更新**。如果需要更新零值,改用 `map[string]any` 方式:
> ```go
> db.Model(&user).Updates(map[string]any{"age": 0}) // ✅ 能更新为 0
> ```
### 批量更新
```go
// 批量更新满足条件的行
db.Model(&User{}).Where("status = ?", "active").Update("role", "vip")
// UPDATE users SET role='vip' WHERE status='active';
```
## 删除(Delete)
```go
// 根据主键删除
db.Delete(&user, 10) // DELETE FROM users WHERE id = 10
// 批量删除
db.Where("age < ?", 18).Delete(&User{})
// 软删除(如果模型包含 DeletedAt)
db.Delete(&user) // 不是 DELETE,而是 UPDATE ... SET deleted_at=NOW()
```
> [!info] 软删除 vs 物理删除
> 物理 `DELETE FROM` 不可逆且丢失审计痕迹。如果你启用了 `SoftDelete`(模型含 `DeletedAt` 字段),默认执行软删除。
>
> 想强制执行物理删除?使用 `Unscoped`:
> ```go
> db.Unscoped().Delete(&user, 10) // 真正的 DELETE
> ```
## 错误处理
```go
result := db.First(&user, 100)
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
fmt.Println("用户不存在")
} else if result.Error != nil {
fmt.Println("数据库错误:", result.Error)
}
```
| 错误常量 | 含义 |
|----------|------|
| `gorm.ErrRecordNotFound` | 查询结果为空 |
| `gorm.ErrInvalidData` | 插入/更新的数据无效 |
| `gorm.ErrTxAlreadyCommitted` | 事务已提交 |
| `gorm.ErrSavedValueNotNull` | 保存了非空字段的零值 |
## CRUD 决策流程图
```mermaid
flowchart TD
A[收到数据操作请求] --> B{操作类型?}
B -->|读取| C{要几条?}
C -->|1 条| D{需要确定性顺序?}
D -->|是| E[First - 按主键升序]
D -->|否/随机| F[Take - 随机一条]
C -->|N 条| G[Find + Where 条件]
B -->|新增| H{单条还是批量?}
H -->|单条| I[Create]
H -->|批量| J[Create slice]
B -->|修改| K{改多少字段?}
K -->|1 个| L[Update col, val]
K -->|部分| M{需要更新零值?}
M -->|是| N[Updates map]
M -->|否| O[Updates struct]
K -->|全部| P[Save]
B -->|删除| Q{启用 SoftDelete?}
Q -->|是| R[Delete - 软删除]
Q -->|否| S[Delete - 物理删除]
style A fill:#4FC08D,color:#fff
style E fill:#3B82F6,color:#fff
style I fill:#8B5CF6,color:#fff
style P fill:#EC4899,color:#fff
style R fill:#F59E0B,color:#000
```
## 关联笔记
- [[01-安装与初始化]]
- [[02-模型定义]]
- [[04-条件查询]]
- [[10-软删除]]
- [[14-错误处理]]