下面文章解析下面两条语句的详细过程
// 从数据库中读取id=1的表项保存到NewUser中
var DB = &gorm.DB{}
db := DB.Model(&User{})
var NewUser User
db.Where("id = ?", 1).First(&NewUser)
首先需要理解gorm带来了什么方便的地方:它将我们定义的结构体User和数据库中的User表对应起来。避免进行逐个变量一一赋值的操作
- 那么gorm是如何进行解析的呢?
依赖于DB结构体中的Statement字段
DB和Statement中几个比较重要的字段在下面列出
type DB struct {
...
Statement *Statement
...
}
// 几个比较重要的字段值
type Statement struct {
*DB
...
Model interface{}
Dest interface{}
Schema *schema.Schema // 保存自定义的User类型
...
}
在gorm进行解析的时候(详见下文),会将User结构的详细内容都保存到Statement.Schema中。
- Model和Dest的作用
Model的值通过.Model(value interface{})方法进行赋值。Model属性暂时保存User类型,为后面进行解析提供方便。(也可以不设置)
Dest的值保存目标结构题的类型,通过.first方法进行赋值
func (db *DB) First(dest interface{}, conds ...interface{}) (tx *DB) {
tx = db.Limit(1).Order(clause.OrderByColumn{
Column: clause.Column{Table: clause.CurrentTable, Name: clause.PrimaryKey},
})
...
tx.Statement.Dest = dest // 此处进行Dest的赋值
return tx.callbacks.Query().Execute(tx)
}
可以看到First方法会执行Execute函数,为什么说Model可以不用设置呢?因为在Execute函数中会判断Model是否为空,如果Model为空,那么就将Dest的类型赋值给Model。
func (p *processor) Execute(db *DB) *DB {
...
// 此处的stmt == db.Statement
if stmt.Model == nil {
stmt.Model = stmt.Dest
} else if stmt.Dest == nil {
stmt.Dest = stmt.Model
}
...
}
- 利用Reflect包进行解析
看到这里,应该有小伙伴会问了,既然用Model和Dest保存了User类型,那为什么还需要Statement.Schema呢?
答案是Model和Dest只是保存了User类型,但是并不知道User类型的具体情况。
就例如:Model和Dest只知道一个人的姓名,但是Schema里面保存了这个人的身高、年龄、体重等等所有的信息。
就像我们可以通过一个人的姓名去找到这个人,然后得到Schema需要的所有信息一样,我们也可以使用Go提供的reflect包解析出我们需要的信息。
下面一些具体的例子展示了如何使用reflect包
var blog Blog
structValue := reflect.ValueOf(blog)
fmt.Printf("structValue value:%vn", structValue) // structValue value:{0 0}
fmt.Printf("structValue type:%vn", structValue.Type()) // structValue type:main.Blog
fmt.Printf("structValue kind:%vn", structValue.Type().Kind()) // structValue kind:struct
fmt.Printf("structValue CanSet:%tn", structValue.CanSet()) // structValue CanSet:false
structPtrValue := reflect.ValueOf(&blog)
fmt.Printf("structPtrValue value:%vn", structPtrValue) // structPtrValue value:&{0 0}
fmt.Printf("structPtrValue type:%vn", structPtrValue.Type()) // structPtrValue type:*main.Blog
fmt.Printf("structPtrValue type Elem:%vn", structPtrValue.Type().Elem()) // structPtrValue type Elem:main.Blog
fmt.Printf("structPtrValue kind:%vn", structPtrValue.Type().Kind()) // structPtrValue kind:ptr
fmt.Printf("structPtrValue CanSet:%tn", structPtrValue.CanSet()) // structPtrValue CanSet:false
fmt.Printf("structPtrValue Elem CanSet:%tn", structPtrValue.Elem().CanSet()) // structPtrValue Elem CanSet:true
明白了reflect包之后,就可以很清楚得看懂源码中的解析过程
在Execute函数中:stmt.Parse(stmt.Model),解析Model,将信息保存到Schema中
跳转进入函数后,进入到下面的函数中
func ParseWithSpecialTableName(dest interface{}, cacheStore *sync.Map, namer Namer, specialTableName string) (*Schema, error) {
...
// modelType也就是我们需要的具体信息,包括但不限于dest结构的字段总数、每个字段的名称、字段的索引等等
modelType = reflect.ValueOf(dest)
// ast.IsExported用于判断该字段是否导出(也就是首字母是否大写)
for i := 0; i < modelType.NumField(); i++ {
if fieldStruct := modelType.Field(i); ast.IsExported(fieldStruct.Name) {
// shema.ParseField将字段信息补充为shema的格式
if field := schema.ParseField(fieldStruct); field.EmbeddedSchema != nil {
schema.Fields = append(schema.Fields, field.EmbeddedSchema.Fields...)
} else {
schema.Fields = append(schema.Fields, field)
}
}
}
...
return schema, err
}
解析完之后,只需要去数据库中将对应字段进行赋值即可。详细过程可以参考callbacks/query.go文件中的Query()函数。



