You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 08:31:08