Go 基于gorm如何处理零值问题解决方案

1,841 阅读5分钟

示例:User结构体

type User struct {
    ID     uint
    Name   *string
    Age    *int
    Status int // 1: 正常, 0: 禁用
}
​
​

1. 使用 Updates 方法

通过 struct 使用 Updates 方法时,GORM 默认会忽略零值字段(如 0""false),除非使用 map 来显式地指定要更新的字段。

示例(struct 方式):

user := User{
    Status: 0, // 零值字段
}
​
db.Model(&User{}).Where("id = ?", 1).Updates(user)
// Status 字段不会被更新,因为它是零值
​

示例(map 方式):

updateData := map[string]interface{}{
    "Name":   "John",  // 更新 Name 字段
    "Age":    30,      // 更新 Age 字段
    "Status": 0,       // 更新 Status 字段,即使它是 0
}
​
//这个例子中,Status 字段会被正确地更新为 0。
db.Model(&User{}).Where("id = ?", 1).Updates(updateData)

2. 使用 Select 方法选择要更新的字段

Select 方法可以指定仅更新特定的字段,即使它们是零值。这样你可以精确控制更新的字段。

func UpdateUserWithSelect(db *gorm.DB, userID uint, user *User) error {
    return db.Model(&User{}).Where("id = ?", userID).
        Select("Name", "Age", "Status").Updates(user).Error
}
​

3. 使用 Omit 方法忽略不需要更新的字段

你也可以使用 Omit 方法来明确忽略特定字段的更新。对于需要更新的零值字段则不应省略。

func UpdateUserWithOmit(db *gorm.DB, userID uint, user *User) error {
    return db.Model(&User{}).Where("id = ?", userID).
        Omit("Name", "Age").Updates(user).Error
}
​

4. 使用 Save 方法(更新所有字段包括零值)

Save 方法将会更新模型中的所有字段,包括零值字段。GORM 会遍历模型中的所有字段并生成相应的 SQL 更新语句。Save 不会忽略任何字段,因此适用于更新所有字段的场景。

user := User{
    ID:     1,
    Name:   "John",
    Age:    30,
    Status: 0, // Save 方法会更新 Status 字段为 0
}
//在这个例子中,Save 方法将会更新 Name、Age 和 Status 字段,无论它们的值是否为零。
db.Save(&user)
​

说明: 该方法针对有创建人、创建时间字段的表,不建议使用,否则每次更新会把创建时间更新为最新时间

5. 通过指针类型字段更新零值

当使用 struct 作为参数传递给 Updates 方法时,GORM 默认会忽略零值(如 0""false),但如果字段是指针类型,GORM 会根据指针是否为 nil 来决定是否更新该字段。这意味着你可以通过指针类型字段来控制是否更新零值。

type User struct {
    ID     uint
    Name   *string
    Age    *int
    Status *int // 指针类型,用于区分零值和未设置
}
​
func UpdateUser(db *gorm.DB, userID uint, user User) error {
    return db.Model(&User{}).Where("id = ?", userID).Updates(user).Error
}
​
status := 0 // 零值
age := 30   // 非零值
​
user := User{
    Status: &status, // 指针类型,即使为零值也会更新
    Age:    &age,    // 指针类型,更新非零值
}
​
err := UpdateUser(db, 1, user)
if err != nil {
    log.Fatal(err)
}

在这个例子中:

  • Status 字段是 *int 类型,并且指向 0。因为 Status 是指针类型,GORM 会将其视为一个有效值,并更新数据库中的 status 字段为 0
  • Age 字段也是 *int 类型,指向 30,因此会更新 age 字段为 30

指针类型字段的优势

使用指针类型的字段有以下几个优势:

  • 区分零值和未设置的字段:指针类型允许你区分字段是否设置了值,即使这个值是零值。例如,*int 类型可以区分 nil(表示未设置)和 0(表示明确的零值)。
  • 避免无意间的忽略:通过指针类型,你可以确保 GORM 在更新时不会忽略需要更新的零值字段。
  • 使用指针类型字段时,必须确保在更新时为这些字段赋值,nil 值表示不更新。

6. 处理time.Time字段零值问题

在 Go 中使用 GORM 时,time.Time 类型的字段在未赋值的情况下会默认为零值(即 0001-01-01 00:00:00)。如果你不希望 GORM 将这个零值写入数据库,你可以通过以下几种方式来处理这个问题。

6.1. 使用指针类型 (*time.Time)

time.Time 字段定义为指针类型 *time.Time。这样,在字段未赋值时,它的值为 nil,而不是默认的零值。GORM 在写入数据库时会跳过 nil 值的字段。

type User struct {
    ID        uint       `gorm:"primaryKey"`
    Name      string
    CreatedAt *time.Time
}
​
user := User{Name: "Alice"}
db.Create(&user) // CreatedAt 不会写入数据库

6.2 在 GORM 中,给字段添加 gorm:"default:null" 标签

gorm:"default:null" 主要用于数据库字段的默认值设置,可以防止写入零值0001-01-01 00:00:00 而是写入NULL,但不能解决 time.Time 零值的问题(即字段本身还是零值,只是不会把零值写入数据库)。

type User struct {
    ID        uint       `gorm:"primaryKey"`
    Name      string
    CreatedAt time.Time  `gorm:"default:null"`
}
user := User{Name: "Alice"}
db.Create(&user) // CreatedAt 不会写入数据库`0001-01-01 00:00:00而是NULL

总结

  • Updates 方法: 使用 map[string]interface{} 方式,可以更新零值字段,适合需要动态更新部分字段的场景。
  • Updates 方法: :可以在 struct 中使用指针类型字段(如 *int*string)来更新零值,这样即使字段值为 0,GORM 也会更新数据库中的相应字段。通过指针类型,你可以精确控制哪些字段被更新,哪些字段被忽略。
  • Save 方法: 更新所有字段,包括零值字段,适合需要更新整个模型的场景。
  • Updates方法: 如果使用 struct 作为参数且不是指针类型字段传递给 Updates 方法,GORM 会默认忽略零值字段。
  • 如果你想处理 nil 与零值的区别,可以考虑使用 map[string]interface{}或者指针 的方式来明确指定要更新的字段
  • 使用指针类型 (*time.Time) 是处理 time.Time 零值问题的推荐方式,可以让 GORM 将未赋值的字段保存为 NULL