Golang中使用GORM如何实现对象的部分更新?
GORM 实现PATCH部分更新Shop记录方案
问题复现
定义的Shop结构体如下:
type Shop struct { ID uint32 `gorm:"primary_key;auto_increment" json:"id"` Name string `gorm:"size:255;not null;unique" json:"name"` Email string `gorm:"size:100;not null;unique" json:"email"` PhoneNumber string `gorm:"size:50;unique" json:"phone_number"` Password string `gorm:"size:100;not null;" json:"password"` CreatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP" json:"created_at"` UpdatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP" json:"updated_at"` }
原有全量更新方法在传入所有字段时可正常运行:
func (shop *Shop) UpdateAShop(db *gorm.DB, uid uint32) (*Shop, error) { // To hash the password err := shop.BeforeSave() if err != nil { log.Fatal(err) } db = db.Debug().Model(&Shop{}).Where("id = ?", uid).Take(&Shop{}).Updates( map[string]interface{}{ "password": shop.Password, "name": shop.Name, "phone_number": shop.PhoneNumber, "email": shop.Email, "update_at": time.Now(), }, ) if db.Error != nil { print("database error") return &Shop{}, db.Error } // This is the display the updated shop err = db.Debug().Model(&Shop{}).Where("id = ?", uid).Take(&shop).Error if err != nil { return &Shop{}, err } return shop, nil }
发起PATCH部分更新请求(仅传name字段)时方法运行失败,返回错误:
{ "error": "Incorrect Details" }
请求示例:
{ "name": "My Shop" }
问题根因
- 硬编码将所有字段写入更新map:部分更新时未传入的字段会取Go类型零值(字符串为空串、数值为0),触发数据库
not null、unique约束,导致更新失败。 - 无条件执行密码哈希:未传Password字段时,会对空字符串做哈希后覆盖数据库原有的正确密码,同时空值校验也会直接报错。
- 字段名拼写错误:更新map中写的
update_at和结构体定义、数据库表字段updated_at不匹配。 - 错误处理逻辑不合理:
log.Fatal会直接终止整个服务进程,没有对参数校验错误、数据库错误做区分处理。
正确实现代码
核心逻辑是先查询原有记录,仅更新请求中携带的非零值字段,仅当密码字段被传入时才执行哈希逻辑:
func (shop *Shop) UpdateAShop(db *gorm.DB, uid uint32) (*Shop, error) { // 第一步:先查询数据库中原有店铺记录 var existingShop Shop if err := db.Where("id = ?", uid).First(&existingShop).Error; err != nil { return nil, errors.New("shop not found") } // 第二步:组装待更新字段map,只更新请求里传了的字段 updateFields := make(map[string]interface{}) // 非密码字段判断:传入值不是零值才加入更新列表 if shop.Name != "" { updateFields["name"] = shop.Name } if shop.Email != "" { updateFields["email"] = shop.Email } if shop.PhoneNumber != "" { updateFields["phone_number"] = shop.PhoneNumber } // 密码字段单独处理:只有传了新密码才做哈希、加入更新列表 if shop.Password != "" { // 调用原有的密码哈希逻辑,不要无条件执行BeforeSave if err := shop.BeforeSave(); err != nil { return nil, errors.New("password process failed") } updateFields["password"] = shop.Password } // 没有需要更新的字段直接返回原记录 if len(updateFields) == 0 { return &existingShop, nil } // 第三步:执行更新,UpdatedAt GORM会自动维护,不需要手动赋值 if err := db.Model(&existingShop).Updates(updateFields).Error; err != nil { return nil, err } // 返回更新后的完整记录 return &existingShop, nil }
额外注意点
- 如果需要支持把字段更新为零值(比如把PhoneNumber设为空),不要用结构体传值更新,用上述map方式,显式把要更新的零值字段加入map即可。
- 不要在模型方法里用
log.Fatal,所有错误都向上返回给上层HTTP处理层,统一返回对应的状态码和错误信息,避免单次请求错误导致整个服务崩溃。 - GORM的
Updates方法用结构体传参时默认会忽略零值字段,用map传参则会更新所有传入的键值,部分更新场景优先用map方式更可控。
内容的提问来源于stack exchange,提问作者Arka-cell
相关产品推荐
相关产品推荐

