更新记录

数据不会一成不变,更新操作必不可少。GORM 提供了多种更新方式,灵活应对不同需求。

Save

Save 保存所有字段,包括零值:

var user User
db.First(&user, 1)

user.Name = "新名字"
user.Age = 0

db.Save(&user)

这会把 nameage 都更新,age 被更新为 0。

Save 是全量更新,会执行 UPDATE ... SET name=?, age=?, ...。如果只想更新部分字段,用 UpdateUpdates

Update

更新单个字段:

db.Model(&User{}).Where("id = ?", 1).Update("name", "新名字")

更新模型实例的字段:

var user User
db.First(&user, 1)
db.Model(&user).Update("name", "新名字")

Updates

更新多个字段,用结构体:

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"})

只更新 nameage,忽略 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", "新名字")

UpdateColumn

跳过钩子和自动时间戳:

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 适合性能敏感场景。