在使用gorm将一个字段更新为空的时候,发现并不生效,不了解具体什么原因,所以此时需要打开debug模式,查看原生SQL是如何执行的。
type Student struct {
Model
Email string `form:"email" json:"email"`
Name string `form:"name" json:"name"`
}
func(c *Content) update(content Content) (err) {
err = db.Model(&Content{}).Debug().Where("id = ?", 123).Update(&content).Error
}
查看日志便知,此时如果name为空字符串,那么update的sql语句中并不会set,后查阅,方知gorm对于空字符和0这种数据,认为是不需要处理的,所以。。。
遇到这个问题,有两种解决方案:
A.更新传值的时候通过map来指定;
B.修改gorm的源码包,让它支持自定义是否可以设置为空值;
上述两种方案,第一种比较简单,不过感觉比较low,所以我选择尝试第二种。
当然第二种,也有它的问题,比如被更新之后,得手动去调整回来。
下面重点讲解第二种方案的实施。
1、找到gorm包下的scope.go文件
func convertInterfaceToMap(values interface{}, withIgnoredField bool, db *DB) map[string]interface{} {
var attrs = map[string]interface{}{}
switch value := values.(type) {
case map[string]interface{}:
return value
case []interface{}:
for _, v := range value {
for key, value := range convertInterfaceToMap(v, withIgnoredField, db) {
attrs[key] = value
}
}
case interface{}:
reflectValue := reflect.ValueOf(values)
switch reflectValue.Kind() {
case reflect.Map:
for _, key := range reflectValue.MapKeys() {
attrs[ToColumnName(key.Interface().(string))] = reflectValue.MapIndex(key).Interface()
}
default:
for _, field := range (&Scope{Value: values, db: db}).Fields() {
if !field.IsBlank && (withIgnoredField || !field.IsIgnored) {
attrs[field.DBName] = field.Field.Interface()
}
}
}
}
return attrs
}
上面代码表示我们传递过来的数据会被转为map型,然后再进行数据库字段更新,这个代码很简单,就是把满足条件保存到map。
我们要解决的是空值能够更新,则和field.IsBlank相关联,接着找到下一个方法;
// Fields get value's fields
func (scope *Scope) Fields() []*Field {
if scope.fields == nil {
var (
fields []*Field
indirectScopeValue = scope.IndirectValue()
isStruct = indirectScopeValue.Kind() == reflect.Struct
)
for _, structField := range scope.GetModelStruct().StructFields {
if isStruct {
fieldValue := indirectScopeValue
for _, name := range structField.Names {
if fieldValue.Kind() == reflect.Ptr && fieldValue.IsNil() {
fieldValue.Set(reflect.New(fieldValue.Type().Elem()))
}
fieldValue = reflect.Indirect(fieldValue).FieldByName(name)
}
//判断model中字段有没有force字段,此处使用Force,是因为解析tag时,统一转成了大写
_, ok := structField.TagSettingsGet("FORCE")
//如果字段为空值,且字段存在则设定为false
fields = append(fields, &Field{StructField: structField, Field: fieldValue, IsBlank: isBlank(fieldValue) && !ok})
} else {
fields = append(fields, &Field{StructField: structField, IsBlank: true})
}
}
scope.fields = &fields
}
return *scope.fields
}
注意上面带注释的两行,作用就不再赘述了。
2、修改我们的model
type Student struct {
Model
Email string `gorm:"force" form:"email" json:"email"`
Name string `gorm:"force" form:"name" json:"name"`
}
func(c *Content) update(content Content) (err) {
err = db.Model(&Content{}).Debug().Where("id = ?", 123).Update(&content).Error
}
此时执行则顺利完成,SQL语句也包含了所有字段。
3、你有没有感到奇怪,为什么scope里面校验的是FORCE,而我在model中则定义的是force?
这就要说到另一个文件 gorm包下的 model_struct.go
func parseTagSetting(tags reflect.StructTag) map[string]string {
setting := map[string]string{}
for _, str := range []string{tags.Get("sql"), tags.Get("gorm")} {
if str == "" {
continue
}
tags := strings.Split(str, ";")
for _, value := range tags {
v := strings.Split(value, ":")
k := strings.TrimSpace(strings.ToUpper(v[0]))
if len(v) >= 2 {
setting[k] = strings.Join(v[1:], ":")
} else {
setting[k] = k
}
}
}
return setting
}
这个方法表示将orm中定义的tag标签,全部解析并转化为大写。
至此,讲完了。