书接上回,上回写道,GORM的查询和创建(插入数据),这回继续些增删改查的改和删的操作。
文章目录
- 更新update
- 修改单个列
- 修改多个列
- 修改选定字段
- 批量更新新
- 阻止全局更新
- 使用 SQL 表达式更新
- `注意`
- 根据子查询进行更新
- 不使用 Hook 和时间追踪
- 返回修改行的数据
- 检查字段是否有改变
- 在 Update 时修改值
- 其他手段更新(save)
- 总结`注意`
更新update
修改单个列
当使用Update更新单个列时,它需要有任何条件,否则会引发错误ErrMissingWhereClause,详情查看Block Global Updates了解详细信息。当使用Model方法并且它的值有一个主值时,主键将被用来构建条件。
根据条件修改
db.Model(&User{}).Where("active = ?", true).Update("name", "hello")
// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE active=true;
当用户ID为111
// User's ID is `111`:
db.Model(&user).Update("name", "hello")
// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111;
更新条件和模型值
db.Model(&user).Where("active = ?", true).Update("name", "hello")
// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111 AND active=true;
修改多个列
Updates
支持使用struct
或map[string]
接口{}进行更新。
注意:
当使用struct
进行更新时,默认情况下只更新非零字段
使用' struct '更新属性,将只更新非零字段
db.Model(&user).Updates(User{Name: "hello", Age: 18, Active: false})
// UPDATE users SET name='hello', age=18, updated_at = '2013-11-17 21:34:10' WHERE id = 111;
使用map来更新属性,或者使用Select来指定要更新的字段这种情况下
使用' map '更新属性
db.Model(&user).Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false})
// UPDATE users SET name='hello', age=18, active=false, updated_at='2013-11-17 21:34:10' WHERE id=111;
修改选定字段
使用' map '更新字段 Select
db.Model(&user).Select("name").Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false})
// UPDATE users SET name='hello' WHERE id=111;
使用' map '更新字段 Qmit
db.Model(&user).Omit("name").Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false})
// UPDATE users SET age=18, active=false, updated_at='2013-11-17 21:34:10' WHERE id=111;
使用Struct 跟新字段
db.Model(&user).Select("Name", "Age").Updates(User{Name: "new_name", Age: 0})
// UPDATE users SET name='new_name', age=0 WHERE id=111;
使用Struct 更新所有字段
包括非0字段
db.Model(&user).Select("*").Updates(User{Name: "李四", Role: "admin", Age: 0})
db.Model(&user).Select("*").Omit("Role").Updates(User{Name: "李四", Role: "admin", Age: 0})
更新通知(钩子)
GORM允许钩子Beforeave
, 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将执行批处理更新
使用struct进行更新
db.Model(User{}).Where("role = ?", "admin").Updates(User{Name: "hello", Age: 18})
// UPDATE users SET name='hello', age=18 WHERE role = 'admin';
使用Map进行更新
db.Table("users").Where("id IN ?", []int{10, 11}).Updates(map[string]interface{}{"name": "hello", "age": 18})
// UPDATE users SET name='hello', age=18 WHERE id IN (10, 11);
阻止全局更新
如果执行一个批处理更新,没有任何条件,GORM将不会运行它,并将返回ErrMissingWhereClause错误默认
这种情况下,必须使用某些条件或使用原始SQL
或启用AllowGlobalUpdate模式
错误案例
db.Model(&User{}).Update("name", "李四").Error // gorm.ErrMissingWhereClause
正确但是又不怎么正确的案例,比如:where 1=1
db.Model(&User{}).Where("1 = 1").Update("name", "李四")
// UPDATE users SET `name` = "李四" WHERE 1=1
正确方法
db.Session(&gorm.Session{AllowGlobalUpdate: true}).Model(&User{}).Update("name", "李四")
// UPDATE users SET `name` = "李四"
db.Exec("UPDATE users SET name = ?", "李四")
// UPDATE users SET name = "李四"
使用 SQL 表达式更新
GORM允许使用SQL表达式更新列
// product's ID is `3`
db.Model(&product).Update("price", gorm.Expr("price * ? + ?", 2, 100))
// UPDATE "products" SET "price" = price * 2 + 100, "updated_at" = '2013-11-17 21:34:10' WHERE "id" = 3;
db.Model(&product).Updates(map[string]interface{}{"price": gorm.Expr("price * ? + ?", 2, 100)})
// UPDATE "products" SET "price" = price * 2 + 100, "updated_at" = '2013-11-17 21:34:10' WHERE "id" = 3;
db.Model(&product).UpdateColumn("quantity", gorm.Expr("quantity - ?", 1))
// UPDATE "products" SET "quantity" = quantity - 1 WHERE "id" = 3;
db.Model(&product).Where("quantity > 1").UpdateColumn("quantity", gorm.Expr("quantity - ?", 1))
// UPDATE "products" SET "quantity" = quantity - 1 WHERE "id" = 3 AND quantity > 1;
GORM还允许使用自定义数据类型更新SQL表达式/上下文值
注意
这种方式很特别
// Create from customized data type
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},
})
// UPDATE `user_with_points` SET `name`="jinzhu",`location`=ST_PointFromText("POINT(100 100)") WHERE `id` = 1
根据子查询进行更新
db.Model(&user).Update("company_name", db.Model(&Company{}).Select("name").Where("companies.id = users.company_id"))
// UPDATE "users" SET "company_name" = (SELECT name FROM companies WHERE companies.id = users.company_id);
db.Table("users as u").Where("name = ?", "李四").Update("company_name", db.Table("companies as c").Select("name").Where("c.id = u.company_id"))
db.Table("users as u").Where("name = ?", "李四").Updates(map[string]interface{}{"company_name": db.Table("companies as c").Select("name").Where("c.id = u.company_id")})
不使用 Hook 和时间追踪
如果你想跳过Hooks
方法并且在更新时不跟踪更新时间,你可以使用UpdateColumn
UpdateColumns
,它的工作原理就像update
, Updates
更新单列
db.Model(&user).UpdateColumn("name", "hello")
// UPDATE users SET name='hello' WHERE id = 111;
更新多列
db.Model(&user).UpdateColumns(User{Name: "hello", Age: 18})
// UPDATE users SET name='hello', age=18 WHERE id = 111;
更新选定的列
db.Model(&user).Select("name", "age").UpdateColumns(User{Name: "hello", Age: 0})
// UPDATE users SET name='hello', age=0 WHERE id = 111;
返回修改行的数据
返回所有行
var users []User
db.Model(&users).Clauses(clause.Returning{}).Where("role = ?", "admin").Update("salary", gorm.Expr("salary * ?", 2))
// UPDATE `users` SET `salary`=salary * 2,`updated_at`="2021-10-28 17:37:23.19" WHERE role = "admin" RETURNING *
返回指定列
db.Model(&users).Clauses(clause.Returning{Columns: []clause.Column{{Name: "name"}, {Name: "salary"}}}).Where("role = ?", "admin").Update("salary", gorm.Expr("salary * ?", 2))
// UPDATE `users` SET `salary`=salary * 2,`updated_at`="2021-10-28 17:37:23.19" WHERE role = "admin" RETURNING `name`, `salary`
检查字段是否有改变
GORM提供了Changed方法,可以在Before Update Hooks(通知)中使用,它将返回字段是否更改。
Changed方法只适用于Update、Updates方法,并且它只检查Update / Updates中的更新值是否等于模型值。如果它被改变并且没有被省略,它将返回true。
func (u *User) BeforeUpdate(tx *gorm.DB) (err error) {
// if Role changed
if tx.Statement.Changed("Role") {
return errors.New("role not allowed to change")
}
if tx.Statement.Changed("Name", "Admin") { // if Name or Role changed
tx.Statement.SetColumn("Age", 18)
}
// if any fields changed
if tx.Statement.Changed() {
tx.Statement.SetColumn("RefreshedAt", time.Now())
}
return nil
}
在 Update 时修改值
要更改Before Hooks中的更新值,你应该使用SetColumn,除非它是一个完整的保存更新和保存(save)
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", "李四")
其他手段更新(save)
db.First(&user)
user.Name = "jinzhu 2"
user.Age = 100
db.Save(&user)
// UPDATE users SET name='jinzhu 2', age=100, birthday='2016-01-01', updated_at = '2013-11-17 21:34:10' WHERE id=111;
Save是一个组合功能。如果save value不包含主键,它将执行Create,否则将执行Update(包含所有字段)。
db.Save(&User{Name: "jinzhu", Age: 100})
// INSERT INTO `users` (`name`,`age`,`birthday`,`update_at`) VALUES ("jinzhu",100,"0000-00-00 00:00:00","0000-00-00 00:00:00")
db.Save(&User{ID: 1, Name: "jinzhu", Age: 100})
// 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
注意
不要使用Save 关于 Model,这是一个错误用法
总结注意
在很多时候修改字段的默认值的时候会失败。其中我遇到过这样的一个情况。就是在使用更新操作的时候。
我们需要将一个非零的字段改成0,此时就出问题了。无法修改。
案例:
mysql.DB.Model(&entiy.Member{}).Where("id=", 6).Update("member_name", "0")
mysql.DB.Model(&entiy.Member{}).Select("member_name").Where("id=?", 3).Updates(&entiy.Member{MemberName: "0"})
mysql.DB.Model(&entiy.Member{}).Where("id = ? ", 4).Updates(map[string]interface{}{"member_name": "0"})
mysql.DB.Save(&entiy.Member{
MemberName: "0",
ID: 5,
})
运行前
运行后
可以发现6号是没有改变的。
推荐用前两种。