保存所有字段

Save 会保存所有的字段,即使字段是零值

  1. db.First(&user)
  2. user.Name = "jinzhu 2"
  3. user.Age = 100
  4. db.Save(&user)
  5. // UPDATE users SET name='jinzhu 2', age=100, birthday='2016-01-01', updated_at = '2013-11-17 21:34:10' WHERE id=111;

保存 是一个组合函数。 如果保存值不包含主键,它将执行 Create,否则它将执行 Update (包含所有字段)。

  1. db.Save(&User{Name: "jinzhu", Age: 100})
  2. // INSERT INTO `users` (`name`,`age`,`birthday`,`update_at`) VALUES ("jinzhu",100,"0000-00-00 00:00:00","0000-00-00 00:00:00")
  3. db.Save(&User{ID: 1, Name: "jinzhu", Age: 100})
  4. // UPDATE `users` SET `name`="jinzhu",`age`=100,`birthday`="0000-00-00 00:00:00",`update_at`="0000-00-00 00:00:00" WHERE `id` = 1

NOTE不要将 SaveModel一同使用, 这是 未定义的行为

更新单个列

当使用 Update 更新单列时,需要有一些条件,否则将会引起ErrMissingWhereClause 错误,查看 阻止全局更新 了解详情。 当使用 Model 方法,并且它有主键值时,主键将会被用于构建条件,例如:

  1. // 根据条件更新
  2. db.Model(&User{}).Where("active = ?", true).Update("name", "hello")
  3. // UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE active=true;
  4. // User 的 ID 是 `111`
  5. db.Model(&user).Update("name", "hello")
  6. // UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111;
  7. // 根据条件和 model 的值进行更新
  8. db.Model(&user).Where("active = ?", true).Update("name", "hello")
  9. // UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111 AND active=true;

更新多列

Updates 方法支持 structmap[string]interface{} 参数。当使用 struct 更新时,默认情况下GORM 只会更新非零值的字段

  1. // 根据 `struct` 更新属性,只会更新非零值的字段
  2. db.Model(&user).Updates(User{Name: "hello", Age: 18, Active: false})
  3. // UPDATE users SET name='hello', age=18, updated_at = '2013-11-17 21:34:10' WHERE id = 111;
  4. // 根据 `map` 更新属性
  5. db.Model(&user).Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false})
  6. // UPDATE users SET name='hello', age=18, active=false, updated_at='2013-11-17 21:34:10' WHERE id=111;

注意 使用 struct 更新时, GORM 将只更新非零值字段。 你可能想用 map 来更新属性,或者使用 Select 声明字段来更新

更新选定字段

如果您想要在更新时选择、忽略某些字段,您可以使用 SelectOmit

  1. // 选择 Map 的字段
  2. // User 的 ID 是 `111`:
  3. db.Model(&user).Select("name").Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false})
  4. // UPDATE users SET name='hello' WHERE id=111;
  5. db.Model(&user).Omit("name").Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false})
  6. // UPDATE users SET age=18, active=false, updated_at='2013-11-17 21:34:10' WHERE id=111;
  7. // 选择 Struct 的字段(会选中零值的字段)
  8. db.Model(&user).Select("Name", "Age").Updates(User{Name: "new_name", Age: 0})
  9. // UPDATE users SET name='new_name', age=0 WHERE id=111;
  10. // 选择所有字段(选择包括零值字段的所有字段)
  11. db.Model(&user).Select("*").Updates(User{Name: "jinzhu", Role: "admin", Age: 0})
  12. // 选择除 Role 外的所有字段(包括零值字段的所有字段)
  13. db.Model(&user).Select("*").Omit("Role").Updates(User{Name: "jinzhu", Role: "admin", Age: 0})

更新 Hook

GORM 支持的 hook 包括:BeforeSave, BeforeUpdate, AfterSave, AfterUpdate. 更新记录时将调用这些方法,查看 Hooks 获取详细信息

  1. func (u *User) BeforeUpdate(tx *gorm.DB) (err error) {
  2. if u.Role == "admin" {
  3. return errors.New("admin user not allowed to update")
  4. }
  5. return
  6. }

批量更新

如果没有通过 Model 指定一个含有主键的记录,GORM 会执行批量更新

  1. // Update with struct
  2. db.Model(User{}).Where("role = ?", "admin").Updates(User{Name: "hello", Age: 18})
  3. // UPDATE users SET name='hello', age=18 WHERE role = 'admin';
  4. // Update with map
  5. db.Table("users").Where("id IN ?", []int{10, 11}).Updates(map[string]interface{}{"name": "hello", "age": 18})
  6. // UPDATE users SET name='hello', age=18 WHERE id IN (10, 11);

阻止全局更新

如果你执行一个没有任何条件的批量更新,GORM 默认不会运行,并且会返回 ErrMissingWhereClause 错误

你可以用一些条件,使用原生 SQL 或者启用 AllowGlobalUpdate 模式,例如:

  1. db.Model(&User{}).Update("name", "jinzhu").Error // gorm.ErrMissingWhereClause
  2. db.Model(&User{}).Where("1 = 1").Update("name", "jinzhu")
  3. // UPDATE users SET `name` = "jinzhu" WHERE 1=1
  4. db.Exec("UPDATE users SET name = ?", "jinzhu")
  5. // UPDATE users SET name = "jinzhu"
  6. db.Session(&gorm.Session{AllowGlobalUpdate: true}).Model(&User{}).Update("name", "jinzhu")
  7. // UPDATE users SET `name` = "jinzhu"

更新的记录数

Get the number of rows affected by a update

  1. // Get updated records count with `RowsAffected`
  2. result := db.Model(User{}).Where("role = ?", "admin").Updates(User{Name: "hello", Age: 18})
  3. // UPDATE users SET name='hello', age=18 WHERE role = 'admin';
  4. result.RowsAffected // returns updated records count
  5. result.Error // returns updating error

高级选项

使用 SQL 表达式更新

GORM 允许用 SQL 表达式更新列,例如:

  1. // product's ID is `3`
  2. db.Model(&product).Update("price", gorm.Expr("price * ? + ?", 2, 100))
  3. // UPDATE "products" SET "price" = price * 2 + 100, "updated_at" = '2013-11-17 21:34:10' WHERE "id" = 3;
  4. db.Model(&product).Updates(map[string]interface{}{"price": gorm.Expr("price * ? + ?", 2, 100)})
  5. // UPDATE "products" SET "price" = price * 2 + 100, "updated_at" = '2013-11-17 21:34:10' WHERE "id" = 3;
  6. db.Model(&product).UpdateColumn("quantity", gorm.Expr("quantity - ?", 1))
  7. // UPDATE "products" SET "quantity" = quantity - 1 WHERE "id" = 3;
  8. db.Model(&product).Where("quantity > 1").UpdateColumn("quantity", gorm.Expr("quantity - ?", 1))
  9. // UPDATE "products" SET "quantity" = quantity - 1 WHERE "id" = 3 AND quantity > 1;

另外,GORM 也允许 SQL 表达式或是带有 自定义数据类型 的 Content Valuer,例如:

  1. // Create from customized data type
  2. type Location struct {
  3. X, Y int
  4. }
  5. func (loc Location) GormValue(ctx context.Context, db *gorm.DB) clause.Expr {
  6. return clause.Expr{
  7. SQL: "ST_PointFromText(?)",
  8. Vars: []interface{}{fmt.Sprintf("POINT(%d %d)", loc.X, loc.Y)},
  9. }
  10. }
  11. db.Model(&User{ID: 1}).Updates(User{
  12. Name: "jinzhu",
  13. Location: Location{X: 100, Y: 100},
  14. })
  15. // UPDATE `user_with_points` SET `name`="jinzhu",`location`=ST_PointFromText("POINT(100 100)") WHERE `id` = 1

根据子查询进行更新

使用子查询更新一个表

  1. db.Model(&user).Update("company_name", db.Model(&Company{}).Select("name").Where("companies.id = users.company_id"))
  2. // UPDATE "users" SET "company_name" = (SELECT name FROM companies WHERE companies.id = users.company_id);
  3. db.Table("users as u").Where("name = ?", "jinzhu").Update("company_name", db.Table("companies as c").Select("name").Where("c.id = u.company_id"))
  4. 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")})

不使用 Hook 和时间追踪

如果你希望更新时跳过 Hook 方法,并且不追踪更新的时间,你可以使用 UpdateColumn, UpdateColumns, 它们的用法类似于 Update, Updates

  1. // Update single column
  2. db.Model(&user).UpdateColumn("name", "hello")
  3. // UPDATE users SET name='hello' WHERE id = 111;
  4. // Update multiple columns
  5. db.Model(&user).UpdateColumns(User{Name: "hello", Age: 18})
  6. // UPDATE users SET name='hello', age=18 WHERE id = 111;
  7. // Update selected columns
  8. db.Model(&user).Select("name", "age").UpdateColumns(User{Name: "hello", Age: 0})
  9. // UPDATE users SET name='hello', age=0 WHERE id = 111;

返回修改行的数据

Returning changed data only works for databases which support Returning, for example:

  1. // return all columns
  2. var users []User
  3. db.Model(&users).Clauses(clause.Returning{}).Where("role = ?", "admin").Update("salary", gorm.Expr("salary * ?", 2))
  4. // UPDATE `users` SET `salary`=salary * 2,`updated_at`="2021-10-28 17:37:23.19" WHERE role = "admin" RETURNING *
  5. // users => []User{{ID: 1, Name: "jinzhu", Role: "admin", Salary: 100}, {ID: 2, Name: "jinzhu.2", Role: "admin", Salary: 1000}}
  6. // return specified columns
  7. db.Model(&users).Clauses(clause.Returning{Columns: []clause.Column{{Name: "name"}, {Name: "salary"}}}).Where("role = ?", "admin").Update("salary", gorm.Expr("salary * ?", 2))
  8. // UPDATE `users` SET `salary`=salary * 2,`updated_at`="2021-10-28 17:37:23.19" WHERE role = "admin" RETURNING `name`, `salary`
  9. // users => []User{{ID: 0, Name: "jinzhu", Role: "", Salary: 100}, {ID: 0, Name: "jinzhu.2", Role: "", Salary: 1000}}

检查字段是否有变更?

GORM provides the Changed method which could be used in Before Update Hooks, it will return whether the field has changed or not.

The Changed method only works with methods Update, Updates, and it only checks if the updating value from Update / Updates equals the model value. It will return true if it is changed and not omitted

  1. func (u *User) BeforeUpdate(tx *gorm.DB) (err error) {
  2. // if Role changed
  3. if tx.Statement.Changed("Role") {
  4. return errors.New("role not allowed to change")
  5. }
  6. if tx.Statement.Changed("Name", "Admin") { // if Name or Role changed
  7. tx.Statement.SetColumn("Age", 18)
  8. }
  9. // if any fields changed
  10. if tx.Statement.Changed() {
  11. tx.Statement.SetColumn("RefreshedAt", time.Now())
  12. }
  13. return nil
  14. }
  15. db.Model(&User{ID: 1, Name: "jinzhu"}).Updates(map[string]interface{"name": "jinzhu2"})
  16. // Changed("Name") => true
  17. db.Model(&User{ID: 1, Name: "jinzhu"}).Updates(map[string]interface{"name": "jinzhu"})
  18. // Changed("Name") => false, `Name` not changed
  19. db.Model(&User{ID: 1, Name: "jinzhu"}).Select("Admin").Updates(map[string]interface{
  20. "name": "jinzhu2", "admin": false,
  21. })
  22. // Changed("Name") => false, `Name` not selected to update
  23. db.Model(&User{ID: 1, Name: "jinzhu"}).Updates(User{Name: "jinzhu2"})
  24. // Changed("Name") => true
  25. db.Model(&User{ID: 1, Name: "jinzhu"}).Updates(User{Name: "jinzhu"})
  26. // Changed("Name") => false, `Name` not changed
  27. db.Model(&User{ID: 1, Name: "jinzhu"}).Select("Admin").Updates(User{Name: "jinzhu2"})
  28. // Changed("Name") => false, `Name` not selected to update

在 Update 时修改值

To change updating values in Before Hooks, you should use SetColumn unless it is a full update with Save, for example:

  1. func (user *User) BeforeSave(tx *gorm.DB) (err error) {
  2. if pw, err := bcrypt.GenerateFromPassword(user.Password, 0); err == nil {
  3. tx.Statement.SetColumn("EncryptedPassword", pw)
  4. }
  5. if tx.Statement.Changed("Code") {
  6. user.Age += 20
  7. tx.Statement.SetColumn("Age", user.Age)
  8. }
  9. }
  10. db.Model(&user).Update("Name", "jinzhu")