更新
保存所有字段
传统 API
Save 在执行更新 SQL 时会保存所有字段
db.First(&user)
user.Name = "jinzhu 2"
user.Age = 100
db.Save(&user)
Save 是一个 upsert 函数:
- 如果值中没有主键,则执行
Create - 如果值中有主键,则先执行 Update(所有字段,通过
Select(*))。 - 如果 Update 后
rows affected = 0,则自动回退到Create。
💡 注意:
Save保证会执行更新或插入操作。
为防止在没有匹配行时意外创建记录,请使用Select(*).Updates()。
db.Save(&User{Name: "jinzhu", Age: 100})
db.Save(&User{ID: 1, Name: "jinzhu", Age: 100})
注意 不要将
Save与Model一起使用,这属于未定义行为。
注意
Save方法已被有意地从泛型 API 中移除,以避免歧义和并发问题。请改用Create或Updates方法。
更新单个列
使用 Update 更新单个列时,需要提供条件,否则会抛出 ErrMissingWhereClause 错误,详情请查看阻止全局更新。
泛型 API
ctx := context.Background()
err := gorm.G[User](db).Where("active = ?", true).Update(ctx, "name", "hello")
err := gorm.G[User](db).Where("id = ?", 111).Update(ctx, "name", "hello")
err := gorm.G[User](db).Where("id = ? AND active = ?", 111, true).Update(ctx, "name", "hello")
传统 API
当使用 Model 方法且其值包含主键时,将使用主键来构建条件,例如:
db.Model(&User{}).Where("active = ?", true).Update("name", "hello")
db.Model(&user).Update("name", "hello")
db.Model(&user).Where("active = ?", true).Update("name", "hello")
更新多个列
Updates 支持使用 struct 或 map[string]interface{} 进行更新,使用 struct 更新时默认只更新非零字段
泛型 API
ctx := context.Background()
rows, err := gorm.G[User](db).Where("id = ?", 111).Updates(ctx, User{Name: "hello", Age: 18, Active: false})
rows, err := gorm.G[map[string]interface{}](db).Table("users").Where("id = ?", 111).Updates(ctx, map[string]interface{}{"name": "hello", "age": 18, "active": false})
传统 API
db.Model(&user).Updates(User{Name: "hello", Age: 18, Active: false})
db.Model(&user).Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false})
注意 使用 struct 更新时,GORM 只会更新非零字段。你可能需要使用
map来更新属性,或使用Select指定要更新的字段
更新选定字段
如果你想在更新时只更新选定的字段或忽略某些字段,可以使用 Select、Omit
泛型 API
ctx := context.Background()
rows, err := gorm.G[User](db).Where("id = ?", 111).Select("name").Updates(ctx, map[string]interface{}{"name": "hello", "age": 18, "active": false})
rows, err := gorm.G[User](db).Where("id = ?", 111).Omit("name").Updates(ctx, map[string]interface{}{"name": "hello", "age": 18, "active": false})
rows, err := gorm.G[User](db).Where("id = ?", 111).Select("Name", "Age").Updates(ctx, User{Name: "new_name", Age: 0})
rows, err := gorm.G[User](db).Where("id = ?", 111).Select("*").Updates(ctx, User{Name: "jinzhu", Role: "admin", Age: 0})
rows, err := gorm.G[User](db).Where("id = ?", 111).Select("*").Omit("Role").Updates(ctx, User{Name: "jinzhu", Role: "admin", Age: 0})传统 API
db.Model(&user).Select("name").Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false})
db.Model(&user).Omit("name").Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false})
db.Model(&user).Select("Name", "Age").Updates(User{Name: "new_name", Age: 0})
db.Model(&user).Select("*").Updates(User{Name: "jinzhu", Role: "admin", Age: 0})
db.Model(&user).Select("*").Omit("Role").Updates(User{Name: "jinzhu", Role: "admin", Age: 0})更新钩子
GORM 允许使用钩子 BeforeSave、BeforeUpdate、AfterSave、AfterUpdate。这些方法将在更新记录时被调用,详情请参考钩子
func (u *User) BeforeUpdate(tx *gorm.DB) (err error) {
if u.Role == "admin" {
return errors.New("admin user not allowed to update")
}
return
}批量更新
如果我们没有使用 Model 指定具有主键值的记录,GORM 将执行批量更新
db.Model(User{}).Where("role = ?", "admin").Updates(User{Name: "hello", Age: 18})
db.Table("users").Where("id IN ?", []int{10, 11}).Updates(map[string]interface{}{"name": "hello", "age": 18})
阻止全局更新
如果你在没有任何条件的情况下执行批量更新,GORM 默认不会执行该操作,而是返回 ErrMissingWhereClause 错误
你必须使用某些条件,或者使用原生 SQL,或者启用 AllowGlobalUpdate 模式,例如:
db.Model(&User{}).Update("name", "jinzhu").Error
db.Model(&User{}).Where("1 = 1").Update("name", "jinzhu")
db.Exec("UPDATE users SET name = ?", "jinzhu")
db.Session(&gorm.Session{AllowGlobalUpdate: true}).Model(&User{}).Update("name", "jinzhu")
更新记录数
获取更新影响的行数
result := db.Model(User{}).Where("role = ?", "admin").Updates(User{Name: "hello", Age: 18})
result.RowsAffected
result.Error 高级用法
使用 SQL 表达式更新
GORM 允许使用 SQL 表达式更新列,例如:
db.Model(&product).Update("price", gorm.Expr("price * ? + ?", 2, 100))
db.Model(&product).Updates(map[string]interface{}{"price": gorm.Expr("price * ? + ?", 2, 100)})
db.Model(&product).UpdateColumn("quantity", gorm.Expr("quantity - ?", 1))
db.Model(&product).Where("quantity > 1").UpdateColumn("quantity", gorm.Expr("quantity - ?", 1))
GORM 还允许使用 SQL 表达式/Context Valuer 配合自定义数据类型进行更新,例如:
type Location struct {
X, Y int
}
func (loc Location) GormValue(ctx context.Context, db *gorm.DB) clause.Expr {
return clause.Expr{
SQL: "ST_PointFromText(?)",
Vars: []interface{}{fmt.Sprintf("POINT(%d %d)", loc.X, loc.Y)},
}
}
db.Model(&User{ID: 1}).Updates(User{
Name: "jinzhu",
Location: Location{X: 100, Y: 100},
})
使用子查询更新
使用子查询更新表
db.Model(&user).Update("company_name", db.Model(&Company{}).Select("name").Where("companies.id = users.company_id"))
db.Table("users as u").Where("name = ?", "jinzhu").Update("company_name", db.Table("companies as c").Select("name").Where("c.id = u.company_id"))
db.Table("users as u").Where("name = ?", "jinzhu").Updates(map[string]interface{}{"company_name": db.Table("companies as c").Select("name").Where("c.id = u.company_id")})跳过钩子/时间跟踪
如果你想在更新时跳过 Hooks 方法且不跟踪更新时间,可以使用 UpdateColumn、UpdateColumns,它们的行为与 Update、Updates 相同
db.Model(&user).UpdateColumn("name", "hello")
db.Model(&user).UpdateColumns(User{Name: "hello", Age: 18})
db.Model(&user).Select("name", "age").UpdateColumns(User{Name: "hello", Age: 0})
从修改的行返回数据
返回已更改的数据仅适用于支持 Returning 的数据库,例如:
var users []User
db.Model(&users).Clauses(clause.Returning{}).Where("role = ?", "admin").Update("salary", gorm.Expr("salary * ?", 2))
db.Model(&users).Clauses(clause.Returning{Columns: []clause.Column{{Name: "name"}, {Name: "salary"}}}).Where("role = ?", "admin").Update("salary", gorm.Expr("salary * ?", 2))
检查字段是否已更改?
GORM 提供了 Changed 方法,可用于更新前的钩子中,它将返回字段是否已更改。
Changed 方法仅适用于 Update、Updates 方法,且仅检查 Update / Updates 的更新值是否等于模型值。如果值已更改且未被忽略,则返回 true
func (u *User) BeforeUpdate(tx *gorm.DB) (err error) {
if tx.Statement.Changed("Role") {
return errors.New("role not allowed to change")
}
if tx.Statement.Changed("Name", "Admin") {
tx.Statement.SetColumn("Age", 18)
}
if tx.Statement.Changed() {
tx.Statement.SetColumn("RefreshedAt", time.Now())
}
return nil
}
db.Model(&User{ID: 1, Name: "jinzhu"}).Updates(map[string]interface{"name": "jinzhu2"})
db.Model(&User{ID: 1, Name: "jinzhu"}).Updates(map[string]interface{"name": "jinzhu"})
db.Model(&User{ID: 1, Name: "jinzhu"}).Select("Admin").Updates(map[string]interface{
"name": "jinzhu2", "admin": false,
})
db.Model(&User{ID: 1, Name: "jinzhu"}).Updates(User{Name: "jinzhu2"})
db.Model(&User{ID: 1, Name: "jinzhu"}).Updates(User{Name: "jinzhu"})
db.Model(&User{ID: 1, Name: "jinzhu"}).Select("Admin").Updates(User{Name: "jinzhu2"})
修改更新值
要在 Before 钩子中修改更新值,除非是使用 Save 进行全量更新,否则应使用 SetColumn,例如:
func (user *User) BeforeSave(tx *gorm.DB) (err error) {
if pw, err := bcrypt.GenerateFromPassword(user.Password, 0); err == nil {
tx.Statement.SetColumn("EncryptedPassword", pw)
}
if tx.Statement.Changed("Code") {
user.Age += 20
tx.Statement.SetColumn("Age", user.Age)
}
}
db.Model(&user).Update("Name", "jinzhu")评论
登录后参与评论
KnowForge