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
2026-04-28 20:02:42 +08:00

235 lines
6.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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-错误处理]]