保存所有字段

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

1. db.First(&user)

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

更新单个列

当使用 Update 更新单个列时,你需要指定条件,否则会返回 ErrMissingWhereClause 错误,查看 Block Global Updates 获取详情。当使用了 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;

5. // User 的 ID 是 `111`
6. db.Model(&user).Update("name", "hello")
7. // UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111;

9. // 根据条件和 model 的值进行更新
10. db.Model(&user).Where("active = ?", true).Update("name", "hello")
11. // UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111 AND active=true;

更新多列

Updates 方法支持 struct 和 map[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;

5. // 根据 `map` 更新属性
6. db.Model(&user).Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false})
7. // UPDATE users SET name='hello', age=18, active=false, updated_at='2013-11-17 21:34:10' WHERE id=111;

注意 当通过 struct 更新时,GORM 只会更新非零字段。 如果您想确保指定字段被更新,你应该使用 Select 更新选定字段,或使用 map 来完成更新操作

更新选定字段

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

1. // 使用 Map 进行 Select
2. // User's ID is `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;

6. db.Model(&user).Omit("name").Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false})
7. // UPDATE users SET age=18, active=false, updated_at='2013-11-17 21:34:10' WHERE id=111;

9. // 使用 Struct 进行 Select(会 select 零值的字段)
10. db.Model(&user).Select("Name", "Age").Updates(User{Name: "new_name", Age: 0})
11. // UPDATE users SET name='new_name', age=0 WHERE id=111;

13. // Select 所有字段(查询包括零值字段的所有字段)
14. db.Model(&user).Select("*").Update(User{Name: "jinzhu", Role: "admin", Age: 0})

16. // Select 除 Role 外的所有字段(包括零值字段的所有字段)
17. db.Model(&user).Select("*").Omit("Role").Update(User{Name: "jinzhu", Role: "admin", Age: 0})

更新 Hook

对于更新操作,GORM 支持 BeforeSave、BeforeUpdate、AfterSave、AfterUpdate 钩子,这些方法将在更新记录时被调用,详情请参阅 钩子

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. // 根据 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';

5. // 根据 map 更新
6. db.Table("users").Where("id IN ?", []int{10, 11}).Updates(map[string]interface{}{"name": "hello", "age": 18})
7. // 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

3. db.Model(&User{}).Where("1 = 1").Update("name", "jinzhu")
4. // UPDATE users SET `name` = "jinzhu" WHERE 1=1

6. db.Exec("UPDATE users SET name = ?", "jinzhu")
7. // UPDATE users SET name = "jinzhu"

9. db.Session(&gorm.Session{AllowGlobalUpdate: true}).Model(&User{}).Update("name", "jinzhu")
10. // UPDATE users SET `name` = "jinzhu"

更新的记录数

获取受更新影响的行数

1. // 通过 `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';

5. result.RowsAffected // 更新的记录数
6. result.Error        // 更新的错误

高级选项

使用 SQL 表达式更新

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

1. // product 的 ID 是 `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;

5. db.Model(&product).Updates(map[string]interface{}{"price": gorm.Expr("price * ? + ?", 2, 100)})
6. // UPDATE "products" SET "price" = price * 2 + 100, "updated_at" = '2013-11-17 21:34:10' WHERE "id" = 3;

8. db.Model(&product).UpdateColumn("quantity", gorm.Expr("quantity - ?", 1))
9. // UPDATE "products" SET "quantity" = quantity - 1 WHERE "id" = 3;

11. db.Model(&product).Where("quantity > 1").UpdateColumn("quantity", gorm.Expr("quantity - ?", 1))
12. // UPDATE "products" SET "quantity" = quantity - 1 WHERE "id" = 3 AND quantity > 1;

并且 GORM 也允许使用 SQL 表达式、自定义数据类型的 Context Valuer 来更新,例如:

1. // 根据自定义数据类型创建
2. type Location struct {
3. X, Y int
4. }

6. func (loc Location) GormValue(ctx context.Context, db *gorm.DB) clause.Expr {
7. return clause.Expr{
8. SQL:  "ST_PointFromText(?)",
9. Vars: []interface{}{fmt.Sprintf("POINT(%d %d)", loc.X, loc.Y)},
10. }
11. }

13. db.Model(&User{ID: 1}).Updates(User{
14. Name:  "jinzhu",
15. Location: Location{X: 100, Y: 100},
16. })
17. // 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);

4. db.Table("users as u").Where("name = ?", "jinzhu").Update("company_name", db.Table("companies as c").Select("name").Where("c.id = u.company_id"))

6. 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. // 更新单个列
2. db.Model(&user).UpdateColumn("name", "hello")
3. // UPDATE users SET name='hello' WHERE id = 111;

5. // 更新多个列
6. db.Model(&user).UpdateColumns(User{Name: "hello", Age: 18})
7. // UPDATE users SET name='hello', age=18 WHERE id = 111;

9. // 更新选中的列
10. db.Model(&user).Select("name", "age").UpdateColumns(User{Name: "hello", Age: 0})
11. // UPDATE users SET name='hello', age=0 WHERE id = 111;

返回修改行的数据

返回被修改的数据,仅适用于支持 Returning 的数据库,例如:

1. // 返回所有列
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}}

7. // 返回指定的列
8. DB.Model(&users).Clauses(clause.Returning{Columns: []clause.Column{{Name: "name"}, {Name: "salary"}}}).Where("role = ?", "admin").Update("salary", gorm.Expr("salary * ?", 2))
9. // UPDATE `users` SET `salary`=salary * 2,`updated_at`="2021-10-28 17:37:23.19" WHERE role = "admin" RETURNING `name`, `salary`
10. // users => []User{{ID: 0, Name: "jinzhu", Role: "", Salary: 100}, {ID: 0, Name: "jinzhu.2", Role: "", Salary: 1000}}

检查字段是否有变更?

GORM 提供了 Changed 方法,它可以被用在 Before Update Hook 里,它会返回字段是否有变更的布尔值

Changed 方法只能与 Update、Updates 方法一起使用,并且它只是检查 Model 对象字段的值与 Update、Updates 的值是否相等,如果值有变更,且字段没有被忽略,则返回 true

1. func (u *User) BeforeUpdate(tx *gorm.DB) (err error) {
2. // 如果 Role 字段有变更
3. if tx.Statement.Changed("Role") {
4. return errors.New("role not allowed to change")
5. }

7. if tx.Statement.Changed("Name", "Admin") { // 如果 Name 或 Role 字段有变更
8. tx.Statement.SetColumn("Age", 18)
9. }

11. // 如果任意字段有变更
12. if tx.Statement.Changed() {
13. tx.Statement.SetColumn("RefreshedAt", time.Now())
14. }
15. return nil
16. }

18. db.Model(&User{ID: 1, Name: "jinzhu"}).Updates(map[string]interface{"name": "jinzhu2"})
19. // Changed("Name") => true
20. db.Model(&User{ID: 1, Name: "jinzhu"}).Updates(map[string]interface{"name": "jinzhu"})
21. // Changed("Name") => false, 因为 `Name` 没有变更
22. db.Model(&User{ID: 1, Name: "jinzhu"}).Select("Admin").Updates(map[string]interface{
23. "name": "jinzhu2", "admin": false,
24. })
25. // Changed("Name") => false, 因为 `Name` 没有被 Select 选中并更新

27. db.Model(&User{ID: 1, Name: "jinzhu"}).Updates(User{Name: "jinzhu2"})
28. // Changed("Name") => true
29. db.Model(&User{ID: 1, Name: "jinzhu"}).Updates(User{Name: "jinzhu"})
30. // Changed("Name") => false, 因为 `Name` 没有变更
31. db.Model(&User{ID: 1, Name: "jinzhu"}).Select("Admin").Updates(User{Name: "jinzhu2"})
32. // Changed("Name") => false, 因为 `Name` 没有被 Select 选中并更新

在 Update 时修改值

若要在 Before 钩子中改变要更新的值,如果它是一个完整的更新,可以使用 Save;否则,应该使用 SetColumn ,例如:

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. }

6. if tx.Statement.Changed("Code") {
7. user.Age += 20
8. tx.Statement.SetColumn("Age", user.Age)
9. }
10. }

12. db.Model(&user).Update("Name", "jinzhu")