栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

gorm中Schema源码解析

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

gorm中Schema源码解析

gorm查询操作过程解析

下面文章解析下面两条语句的详细过程

// 从数据库中读取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()函数。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1013055.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号