• 发生时间:2022-10-26

  • 问题描述:同事在调试产线平台前端时,将「设备类型」某条记录的「备注」清空、提交,然后再次查询,发现「备注」并没有被清空。

  • 原因分析:

    • 在测试环境重复同事的操作,发现确实无法清空「备注」,但可以修改「备注」为任何非空的字符串。看来问题与「空字符串」有一定关系。
    • 经过网上调查,在 stack overflow 发现一个知识点——当使用 struct 来更新数据库记录时,gorm 只会更新 struct 中取值为非零值的字段。

    NOTE When update with struct, GORM will only update non-zero fields, you might want to use map to update attributes or use Select to specify fields to update.

    • 由于我正是使用 struct 来更新的,因此空的「备注」字段直接被 gorm 无视了,导致「备注」的内容没被清空。
    // UpdateOrCreate updates an entry if it exists, or creates it if it doesn't exist.
    func UpdateOrCreate(entry Modeler) error {
        entry.SetUpdateTime(time.Now())
        id := entry.GetID()
        db := GetDatabaseConnection()
        if result := db.Model(entry).Where("id = ?", id).Updates(entry); result.Error != nil {
            logs.Errorf("db.Update(factory) error: %s. entry: %+v", result.Error, entry)
            return result.Error
        } else if result.RowsAffected == 0 {
            logs.Warnf("entry not found. Now we try to create it. entry: %+v", entry)
    
            if err := db.Create(entry).Error; err != nil {
                logs.Errorf("db.Create error: %s. entry: %+v", err, entry)
                return err
            }
        }
    
        return nil
    }
    
  • 解决方案:

    • 如上所述,有两种解决方案:改用 map 数据结构,或使用 Select 来指定需要更新的字段。
    • 由于方案二对现有代码的改动更小,因此我最终选择了方案二,如下所示。
    // UpdateOrCreate updates an entry if it exists, or creates it if it doesn't exist.
    func UpdateOrCreate(entry Modeler) error {
        entry.SetUpdateTime(time.Now())
        id := entry.GetID()
        db := GetDatabaseConnection()
        // Use `Select` to select what we want to update, and use `Omit` to skip what we don't want to update.
        if result := db.Model(entry).Where("id = ?", id).Select("*").Omit("created_at").Updates(entry); result.Error != nil {
            logs.Errorf("db.Update(factory) error: %s. entry: %+v", result.Error, entry)
            return result.Error
        } else if result.RowsAffected == 0 {
            logs.Warnf("entry not found. Now we try to create it. entry: %+v", entry)
    
            if err := db.Create(entry).Error; err != nil {
                logs.Errorf("db.Create error: %s. entry: %+v", err, entry)
                return err
            }
        }
    
        return nil
    }
    
  • 实施结果:采用上述方案后,在产线平台前端可以清空任意数据中的「备注」字段。

  • 经验总结:

    • 在 gorm 中使用 struct 来更新数据库记录时,默认不会更新其中的零值字段。编程时需要注意这个坑。