Gorm(三)更新操作
在 Gorm 中,Save、Updates 和 UpdateColumn 是用于更新数据的核心方法,它们的行为和适用场景不同,以下是详细说明:
一、Save:全字段更新(包含零值)
Save 会根据记录的主键(如 ID)更新整条记录,包含所有字段(即使字段为零值或未修改),适用于需要全量覆盖的场景。
基本用法
// 1. 先查询出要更新的记录(确保主键存在)
var user User
db.First(&user, 1) // 查询 ID=1 的用户// 2. 修改字段(包括部分字段)
user.Name = "Alice Updated"
user.Age = 0 // 零值也会被更新// 3. 全字段更新
result := db.Save(&user)// 结果查看
fmt.Println("影响行数:", result.RowsAffected) // 1(成功更新)
fmt.Println("错误:", result.Error)
特性
- 基于主键更新:必须确保结构体中包含主键(如
ID),否则会被视为插入操作(INSERT)。 - 更新所有字段:无论字段是否有变化,都会被写入数据库(包括
0、""等零值)。 - 触发钩子:会执行
BeforeSave、AfterSave等钩子函数(如果定义)。 - 自动更新
UpdatedAt:无需手动设置,Save会自动更新该字段。
二、Updates:部分字段更新(忽略零值,支持 struct/map)
Updates 用于更新部分字段,默认忽略零值(仅更新非零字段),支持通过结构体或 map 指定更新内容,更灵活高效。
1. 通过结构体更新(忽略零值)
// 更新 ID=1 的用户,仅更新非零字段
result := db.Model(&User{}).Where("id = ?", 1).Updates(User{Name: "Bob Updated", // 非零值,会被更新Age: 0, // 零值,默认被忽略(不会更新 age 字段)
})
// SQL: UPDATE users SET name='Bob Updated', updated_at='2024-05-01' WHERE id=1
注意:
- 结构体中零值字段会被忽略(如需强制更新零值,需用
map或Select指定字段)。 - 需通过
Model或Where指定更新条件(否则可能更新全表,非常危险)。
2. 通过 map 更新(包含零值)
map[string]interface{} 可以强制更新零值,适合需要更新零值或动态字段的场景:
// 使用 map 更新,零值会被执行
result := db.Model(&User{}).Where("id = ?", 1).Updates(map[string]interface{}{"name": "Charlie Updated","age": 0, // 零值会被更新到数据库
})
// SQL: UPDATE users SET name='Charlie Updated', age=0, updated_at='2024-05-01' WHERE id=1
3. 强制更新指定字段(包括零值)
如果用结构体更新时需要包含零值,可通过 Select 显式指定字段:
// 强制更新 name 和 age(即使 age 是零值)
result := db.Model(&User{}).Where("id = ?", 1).Select("name", "age").Updates(User{Name: "Dave Updated",Age: 0, // 被 Select 指定,会更新
})
特性
- 部分更新:只更新指定的非零字段(结构体)或所有键值对(map),效率更高。
- 条件更新:必须通过
Where或Model(含主键)指定条件,避免全表更新。 - 触发钩子:会执行
BeforeUpdate、AfterUpdate等钩子函数。 - 自动更新
UpdatedAt:同Save。
三、UpdateColumn / UpdateColumns:绕过钩子的字段更新
UpdateColumn(单字段)和 UpdateColumns(多字段)用于直接更新字段,不触发任何钩子函数,也不会自动更新 UpdatedAt,适合需要高性能或跳过业务逻辑的场景。
1. UpdateColumn:更新单个字段
// 更新 ID=1 的用户的 name 字段,不触发钩子,不更新 UpdatedAt
result := db.Model(&User{}).Where("id = ?", 1).UpdateColumn("name", "Eve Updated")
// SQL: UPDATE users SET name='Eve Updated' WHERE id=1
2. UpdateColumns:更新多个字段
// 更新多个字段,同样绕过钩子
result := db.Model(&User{}).Where("id = ?", 1).UpdateColumns(map[string]interface{}{"name": "Frank Updated","age": 30,
})
// SQL: UPDATE users SET name='Frank Updated', age=30 WHERE id=1
特性
- 无钩子:不执行
BeforeUpdate、AfterUpdate等钩子,适合纯数据更新。 - 不更新
UpdatedAt:需手动指定才会更新该字段(如UpdateColumns(map[string]interface{}{"updated_at": time.Now()}))。 - 高效:减少额外逻辑处理,性能优于
Save和Updates。 - 支持条件和表达式:可结合
Where或 SQL 表达式(如自增):// 年龄自增 1 db.Model(&User{}).Where("id = ?", 1).UpdateColumn("age", gorm.Expr("age + 1"))
对比与适用场景
| 方法 | 更新范围 | 零值处理 | 触发钩子 | 更新 UpdatedAt | 适用场景 |
|---|---|---|---|---|---|
Save | 全字段(基于主键) | 包含零值 | 是 | 是 | 全量覆盖更新(如完整编辑) |
Updates | 部分字段(struct/map) | 结构体忽略零值,map 包含 | 是 | 是 | 常规部分更新(如修改部分属性) |
UpdateColumn | 单个/多个字段 | 包含零值 | 否 | 否 | 高性能更新、跳过业务逻辑 |
注意事项
- 更新条件:使用
Updates、UpdateColumn时,务必通过Where或Model(含主键)指定条件,否则可能导致全表更新(极其危险)。 - 结构体 vs map:结构体更新简洁但忽略零值,map 可强制更新零值,按需选择。
- 钩子影响:如果业务逻辑依赖钩子(如数据校验、日志记录),避免使用
UpdateColumn。
根据更新需求(全量/部分、是否触发钩子、是否包含零值)选择合适的方法,可确保代码高效且符合业务逻辑。
