[toc]
Воспроизвести код
Условия срабатывания этого кода относительно строгие: во-первых, необходимо убедиться, что строка, выполняемая gorm, должна бытьupdates
заявление, и вupdates(struct)
, а входящий thisstruct
Должна содержать полиморфную таблицу, которая прямо или косвенно связана, и эти условия обязательны.
type A struct {
gorm.Model
Name string
B B `gorm:"polymorphic:Owner"`
}
type B struct {
gorm.Model
OwnerID uint
OwnerType string
}
a := A{Name: "test"}
db = db.Model(&a)
db.Where(a)
db.Updates(A{Name: "test2"}) // panic
Феномен
Давайте сначала поговорим о явлении, В процессе совместной отладки кода обнаружено, что при вызове интерфейса обновления будет сообщено об ошибке 500 (паника), но если ничего не изменено, интерфейс вызывается снова и обновление успешно.
Журнал ошибок выглядит следующим образом из-за вызова с нулевым указателем:
panic: runtime error: invalid memory address or nil pointer dereference
github.com/jinzhu/gorm.(*DB).clone(0x0, 0x0)
E:/SoftFile/GOPATH/src/github.com/jinzhu/gorm/main.go:715 +0x4e
github.com/jinzhu/gorm.(*DB).Model(0x0, 0x6cb460, 0xc00017c160, 0x0)
E:/SoftFile/GOPATH/src/github.com/jinzhu/gorm/main.go:445 +0x32
github.com/jinzhu/gorm.(*Scope).TableName(0xc000172100, 0xc00016c3c0, 0x6f894a)
E:/SoftFile/GOPATH/src/github.com/jinzhu/gorm/scope.go:325 +0x133
github.com/jinzhu/gorm.(*Scope).GetModelStruct.func2(0xc000170010, 0xc000172100, 0xc00017a050, 0xc0001749c0)
E:/SoftFile/GOPATH/src/github.com/jinzhu/gorm/model_struct.go:420 +0x24c7
github.com/jinzhu/gorm.(*Scope).GetModelStruct(0xc000172100, 0xc00017a050)
E:/SoftFile/GOPATH/src/github.com/jinzhu/gorm/model_struct.go:574 +0x140c
github.com/jinzhu/gorm.(*Scope).Fields(0xc000172100, 0xc000172100, 0x2030000, 0x2030000)
E:/SoftFile/GOPATH/src/github.com/jinzhu/gorm/scope.go:115 +0xaf
github.com/jinzhu/gorm.convertInterfaceToMap(0x6cb460, 0xc00017c160, 0xc00017c001, 0x199)
E:/SoftFile/GOPATH/src/github.com/jinzhu/gorm/scope.go:860 +0x4f8
github.com/jinzhu/gorm.(*Scope).updatedAttrsWithValues(0xc000172080, 0x6cb460, 0xc00017c160, 0x6cb460, 0xc00017c160)
E:/SoftFile/GOPATH/src/github.com/jinzhu/gorm/scope.go:877 +0x8b
github.com/jinzhu/gorm.assignUpdatingAttributesCallback(0xc000172080)
E:/SoftFile/GOPATH/src/github.com/jinzhu/gorm/callback_update.go:25 +0x81
github.com/jinzhu/gorm.(*Scope).callCallbacks(0xc000172080, 0xc00012ff00, 0x9, 0x10, 0xc00017c160)
E:/SoftFile/GOPATH/src/github.com/jinzhu/gorm/scope.go:831 +0x5c
github.com/jinzhu/gorm.(*DB).Updates(0xc00017e090, 0x6cb460, 0xc00017c160, 0x0, 0x0, 0x0, 0xc00017e090)
E:/SoftFile/GOPATH/src/github.com/jinzhu/gorm/main.go:383 +0x13b
main.main()
E:/SoftFile/GOPATH/src/github.com/mohuishou/test/main.go:34 +0x269
отладка
Во-первых, глядя вниз с вершины стека паники, это вызовs.db
Когда сообщается об ошибке, делается вывод, что она должна бытьs
значениеnil
ошибка вызвана
func (s *DB) clone() *DB {
db := &DB{
db: s.db, // 从这一行开始panic
}
}
Тогда посмотри вниз, здесьs
тоже должен быть одинnil
// Model specify the model you would like to run db operations
// // update all users's name to `hello`
// db.Model(&User{}).Update("name", "hello")
// // if user's primary key is non-blank, will use it as condition, then will only update the user's name to `hello`
// db.Model(&user).Update("name", "hello")
func (s *DB) Model(value interface{}) *DB {
c := s.clone() // 从这里调用
c.Value = value
return c
}
Затем перейдите, когда вы получите имя таблицы, вам нужно позвонитьscope.db.Model
, где db должен бытьnil
привести к сбою вызова
// TableName return table name
func (scope *Scope) TableName() string {
// ...
return scope.GetModelStruct().TableName(scope.db.Model(scope.Value))
}
Из следующего звонка видно, что фактическое приобретениеStruct
, за счет полиморфных ассоциаций (polymorphic
), поэтому вам нужно получить полиморфную таблицуTableName
func (scope *Scope) GetModelStruct() *ModelStruct {
// ...
if polymorphic := field.TagSettings["POLYMORPHIC"]; polymorphic != "" {
if value, ok := field.TagSettings["POLYMORPHIC_VALUE"]; ok {
relationship.PolymorphicValue = value
} else {
// 这里调用
relationship.PolymorphicValue = scope.TableName()
}
}
}
перечислитьGetModelStruct
Причина в том, что вам нужно получитьvalue
Все поля , когда поле равно nil, оно будет называтьсяGetModelStruct
получить
// Fields get value's fields
func (scope *Scope) Fields() []*Field {
if scope.fields == nil {
// ...
for _, structField := range scope.GetModelStruct().StructFields {
// ...
}
}
}
Глядя на эту функцию, вы можете обнаружить, что онаinterface
Преобразовать вmap
, так как мы изначально прошли вdb.Updates(A{Name: "test2"})
Условие представляет собой структуру, поэтому будет выполняться следующееcase interface{} -> default
филиал. позвоню(&Scope{Value: values}).Fields()
, то можно найтиScope
Когда этот объект инициализируется, нетdb
Это поле, поэтому при получении таблицы
Когда нужно назвать имяscope.db
тогда паникуй
func convertInterfaceToMap(values interface{}, withIgnoredField bool) 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) {
attrs[key] = value
}
}
case interface{}:
reflectValue := reflect.ValueOf(values)
switch reflectValue.Kind() {
case reflect.Map:
for _, key := range reflectValue.MapKeys() {
attrs[ToDBName(key.Interface().(string))] = reflectValue.MapIndex(key).Interface()
}
default:
// 在这里调用
for _, field := range (&Scope{Value: values}).Fields() {
if !field.IsBlank && (withIgnoredField || !field.IsIgnored) {
attrs[field.DBName] = field.Field.Interface()
}
}
}
}
return attrs
}
На данный момент ошибка закрыта, но давайте посмотрим, почему вызывается эта функция.
Эта функция получит поля, которые необходимо обновитьmap
, если входящийstruct
, который преобразуется вmap
func (scope *Scope) updatedAttrsWithValues(value interface{}) (results map[string]interface{}, hasUpdate bool) {
if scope.IndirectValue().Kind() != reflect.Struct {
return convertInterfaceToMap(value, false), true
}
results = map[string]interface{}{}
for key, value := range convertInterfaceToMap(value, true) {}
}
Этот метод сохранит карту, которую необходимо обновить.
// assignUpdatingAttributesCallback assign updating attributes to model
func assignUpdatingAttributesCallback(scope *Scope) {
if attrs, ok := scope.InstanceGet("gorm:update_interface"); ok {
if updateMaps, hasUpdate := scope.updatedAttrsWithValues(attrs); hasUpdate {
scope.InstanceSet("gorm:update_attrs", updateMaps)
} else {
scope.SkipLeft()
}
}
}
решение
Суммировать
GORM, используйте карту вместо структуры
При использовании GORM, когда вам нужно обновить некоторые поляЛучше использовать карту вместо структуры, потому что если используется struct, то gorm со временем преобразует структуру в карту, а если структура содержит какие-то ассоциации, то gorm всегда будет искать и преобразовывать рекурсивно, если ассоциации всей таблицы сложные, то это приведет к низкой эффективности.
Почему не надо модифицировать код, второй запуск не паникует
Это связано с тем, что GORM имеет глобальный кеш для структуры структуры.modelStructsMap
, потому что это связано с тем, что при поиске связи ассоциации сообщается об ошибке, она создала новую.modelstruct
И он кэшируется, поэтому следующий код не будет выполняться при повторном вызове.
// GetModelStruct get value's model struct, relationships based on struct and tag definition
func (scope *Scope) GetModelStruct() *ModelStruct {
//...
// Get Cached model struct
if value := modelStructsMap.Get(reflectType); value != nil {
return value
}
// ...
}
Сводка отладки
Процесс отладки намного сложнее написания, потому что отладка начинается с собственного кода, черезGoland
Дебаг постоянно ломает точку, выполняет ее снова и снова, и находит весь процесс выполнения, что приводит к игнорированию самого прямого пути поиска кода ошибки.
Однако это тоже ценный опыт.После отладки этого бага,Goland
Мощная функция отладки уже может воспроизводить сравнение 6