数据不会一成不变,更新操作必不可少。GORM 提供了多种更新方式,灵活应对不同需求。
Save 保存所有字段,包括零值:
var user User
db.First(&user, 1)
user.Name = "新名字"
user.Age = 0
db.Save(&user)
这会把 name 和 age 都更新,age 被更新为 0。
Save 是全量更新,会执行 UPDATE ... SET name=?, age=?, ...。如果只想更新部分字段,用 Update 或 Updates。
更新单个字段:
db.Model(&User{}).Where("id = ?", 1).Update("name", "新名字")
更新模型实例的字段:
var user User
db.First(&user, 1)
db.Model(&user).Update("name", "新名字")
更新多个字段,用结构体:
db.Model(&user).Updates(User{Name: "新名字", Age: 30})
结构体会忽略零值,Age: 0 不会被更新。
用 map 可以更新零值:
db.Model(&user).Updates(map[string]interface{}{
"Name": "新名字",
"Age": 0,
})
用 Select 指定要更新的字段:
db.Model(&user).Select("name", "age").Updates(User{Name: "新名字", Age: 30, Role: "admin"})
只更新 name 和 age,忽略 role。
用 Omit 排除字段:
db.Model(&user).Omit("role").Updates(User{Name: "新名字", Age: 30, Role: "admin"})
用 SQL 表达式更新:
db.Model(&user).Update("age", gorm.Expr("age + ?", 1))
批量更新:
db.Model(&User{}).Where("age < ?", 20).Update("age", gorm.Expr("age * ?", 2))
多个表达式:
db.Model(&user).Updates(map[string]interface{}{
"age": gorm.Expr("age + ?", 1),
"updated_at": gorm.Expr("NOW()"),
})
Select 选择要更新的字段:
db.Model(&user).Select("name").Updates(map[string]interface{}{
"name": "新名字",
"age": 30,
})
只更新 name,忽略 age。
Omit 排除字段:
db.Model(&user).Omit("name").Updates(map[string]interface{}{
"name": "新名字",
"age": 30,
})
更新 age,忽略 name。
根据条件更新:
db.Model(&User{}).Where("age > ?", 30).Update("status", "senior")
更新多条记录:
db.Model(&User{}).Where("role = ?", "user").Updates(map[string]interface{}{
"role": "member",
})
更新关联字段需要用 Select:
user := User{
Name: "张三",
Profile: Profile{Address: "新地址"},
}
db.Select("Profile").Updates(&user)
GORM 自动更新 UpdatedAt 字段:
type User struct {
ID uint
Name string
UpdatedAt time.Time
}
db.Model(&user).Update("name", "新名字")
如果不想自动更新,用 UpdateColumn:
db.Model(&user).UpdateColumn("name", "新名字")
跳过钩子和自动时间戳:
db.Model(&user).UpdateColumn("name", "新名字")
db.Model(&user).UpdateColumns(User{Name: "新名字", Age: 30})
性能更好,但失去钩子和自动时间戳的功能。
更新所有符合条件的记录:
db.Model(&User{}).Where("status = ?", "active").Updates(map[string]interface{}{
"status": "inactive",
})
不带条件会更新所有记录:
db.Model(&User{}).Updates(map[string]interface{}{
"status": "inactive",
})
这很危险,生产环境要谨慎。
获取更新的行数:
result := db.Model(&User{}).Where("age < ?", 20).Update("status", "junior")
fmt.Println(result.RowsAffected)
更新操作会触发钩子:
func (u *User) BeforeUpdate(tx *gorm.DB) error {
if u.Age < 0 {
return errors.New("年龄不能为负数")
}
return nil
}
func (u *User) AfterUpdate(tx *gorm.DB) error {
log.Printf("用户 %s 已更新", u.Name)
return nil
}
UpdateColumn 不会触发钩子。
记录不存在
更新不存在的记录,RowsAffected 为 0:
result := db.Model(&User{}).Where("id = ?", 999).Update("name", "测试")
fmt.Println(result.RowsAffected)
零值不更新
用结构体更新时,零值字段被忽略:
db.Model(&user).Updates(User{Age: 0})
age 不会被更新。用 map 解决:
db.Model(&user).Updates(map[string]interface{}{"age": 0})
主键丢失
模型实例必须有主键才能更新:
user := User{Name: "测试"}
db.Model(&user).Update("name", "新名字")
这会更新所有记录!因为 user 没有 ID。
正确做法:
user := User{ID: 1, Name: "测试"}
db.Model(&user).Update("name", "新名字")
更新操作要注意零值处理和条件限制。Updates 配合 map 能解决大部分问题,UpdateColumn 适合性能敏感场景。