前言
在使用GORM映射到自定义结构体时候出现个别字段无法映射成功
一、排查问题过程
在定义字段时候有些有些英文无法第一眼识别出大小写导致没办法快速定位到问题,我使用gorm的Scan方法进入查看其运行方式。
1.1 gorm scan方法
gorm的scan具体方法如下
if rows, err := tx.Rows(); err == nil {
if rows.Next() {
tx.ScanRows(rows, dest)
} else {
tx.RowsAffected = 0
}
tx.AddError(rows.Close())
}
查看 ScanRow方法
for tx.Statement.ReflectValue.Kind() == reflect.Ptr {
elem := tx.Statement.ReflectValue.Elem()
if !elem.IsValid() {
elem = reflect.New(tx.Statement.ReflectValue.Type().Elem())
tx.Statement.ReflectValue.Set(elem)
}
tx.Statement.ReflectValue = elem
}
1.2 具体分析
分析后初步认为是使用反射(reflection)时候没有获取数据。
在 GORM 中,tx.ScanRows(rows, dest) 方法负责将查询结果映射到目标结构体或结构体切片。这个方法是在执行原始 SQL 查询并获取到 rows 对象后调用的,用于将数据库中的行数据扫描到 Go 的变量中。
以下是 tx.ScanRows(rows, dest) 方法的工作原理:
- 参数
- rows:一个 *sql.Rows 对象,它包含了查询结果。
- dest:目标变量,可以是一个结构体指针或结构体切片指针。
- 工作流程:
- GORM 会遍历 rows 中的每一行数据。
- 对于每一行,GORM 会读取所有的列值。
- 然后,GORM 会根据目标结构体(dest)的字段信息,将列值映射到对应的结构体字段上。
- 映射过程:
- GORM 使用反射(reflection)来检查目标结构体的字段信息,包括字段名、类型和标签(tags)。
- GORM 会将数据库列名与结构体字段名进行匹配。如果列名与字段名不完全相同,GORM 会查找结构体字段的 gorm 标签中的 column 子标签,以确定正确的列名。
- 对于每个匹配的字段,GORM 会调用相应的 Scan 方法(如 sql.Scanner 接口的 Scan 方法),将列值转换为字段类型并赋值给结构体字段。
- 错误处理:
- 如果在扫描过程中遇到错误,GORM 会将错误添加到事务(tx)的错误列表中。
- 在遍历完所有行之后,GORM 会关闭 rows 对象,并将关闭操作中可能发生的错误也添加到事务的错误列表中。
我认为是我的字段名称编写有问题,无论我的`gorm:"column 定义成任何字段都无法成功映射数据,我尝试更换字段名称,运行后可以正常映射,此时我退回之前定义方式,发现自定首字母是小写。
二、结构体定义字段小驼峰和大驼峰有什么区别
在 Go 语言中,结构体字段的命名约定通常遵循以下规则:
-
小驼峰命名法(lowerCamelCase):这是 Go 语言中最常用的命名约定。字段名以小写字母开头,后续每个单词的首字母大写。例如:userName、createdAt。
-
大驼峰命名法(UpperCamelCase 或 PascalCase):这种命名法通常用于类型名(如结构体、接口、函数名等),字段名以大写字母开头,后续每个单词的首字母也大写。例如:UserName、CreatedAt。
这两种命名法在 Go 语言中有以下区别:
可见性
- 小驼峰命名法:字段名首字母小写,表示该字段是私有的,只能在定义它的包内部访问。
- 大驼峰命名法:字段名首字母大写,表示该字段是公开的,可以在其他包中访问。
三、驼峰定义对GORM字段映射的影响
换一种描述可以是:字段私有化,使用反射的时候的影响
在 Go 语言中,如果你使用反射(reflection)来获取结构体的字段,私有字段(即首字母小写的字段)是可以被获取的,但是你不能直接访问它们的值。这是因为 Go 语言的访问控制规则限制了对私有字段的直接访问。
然而,你仍然可以使用反射来获取私有字段的信息(如字段名、类型等),只是不能直接读取或修改它们的值。如果你需要访问私有字段的值,你需要使用 reflect.ValueOf 和 Elem 方法来获取指向结构体的指针的反射值,然后使用 FieldByName 方法来获取私有字段的反射值。
以下是一个示例:
package main
import (
"fmt"
"reflect"
)
type User struct {
ID int `json:"id"`
Name string `json:"name"`
}
func main() {
user := User{ID: 1, Name: "Alice"}
// 获取结构体的反射值
userValue := reflect.ValueOf(&user).Elem()
// 获取私有字段 Name 的反射值
nameField := userValue.FieldByName("Name")
// 检查是否找到了字段
if nameField.IsValid() {
// 获取字段的值
name := nameField.Interface().(string)
fmt.Println("Name:", name)
} else {
fmt.Println("Field not found")
}
}
在这个示例中,我们使用 reflect.ValueOf(&user).Elem() 获取指向 User 结构体的指针的反射值,然后使用 FieldByName(“Name”) 获取私有字段 Name 的反射值。由于我们使用了指针,所以可以访问私有字段的值。
请注意,这种方法违反了 Go 语言的封装原则,可能会导致代码难以维护和理解。在实际开发中,建议尽量使用公开的字段和方法,并遵循 Go 语言的访问控制规则。