今天是【7天从零实现TORM框架】的第二天,主要任务是:
- 使用反射(reflect)获取struct结构体中的字段属性,将其映射到数据库中的表信息。代码100行左右。
若对Go中反射的使用不了解的话,我写了三篇关于反射的文章,给小伙伴提供参考,足以应对本项目中所使用的反射知识点。
- go反射第一弹:https://mp.weixin.qq.com/s/F8yZyqC5UwoewsX0THqy1w
- go反射第二弹:https://mp.weixin.qq.com/s/lgZykTL8ls6aG0OMNSbZMw
- go反射第三弹:https://mp.weixin.qq.com/s/vFt06c9herwTrx1LTxNaKg
源代码:在【迈莫coding】中回复关键字「 torm 」获取github地址链接。
后续会为【七天从零实现TORM框架】录制视频,文章+视频+代码
对象表结构映射介绍
在我们编码过程中,无论使用那种语言,只要涉及到与数据库交互,就需要将语言层面的对象转换为数据库所支持的格式。而Schema 类就是支持该功能,将任意对象(Object)转换成数据库中的表(Table)结构。
例如,在我们Go语言中,存储用户的相关信息,会使用如下结构体来进行存储。
type User struct {
Name string `torm:"user_name,varchar"`
Age int
}
若要与数据库进行交互,则需要转换成如下的数据机构。
INSERT INTO user(user_name, age) VALUES("迈莫coding", 1);
数据库表结构与语言层面对象的对应关系
- 字段名 – 成员变量 Tag 属性中的第一个字段
- 字段类型 – 成员变量 Tag 属性中的第二个字段
Schema实现
源代码存放在根目录下 schema.go 文件中
//schema.go
package session
import (
"go/ast"
"reflect"
"strings"
"sync"
)
//struct 标签解析结果
type Filed struct {
Name string // 字段名
i int // 位置
Type string // 字段类型
TableColumn string // 对应数据库表列名
Tag string // 约束条件
}
// Schema 主要包含被映射的字段(Fields)
type Schema struct {
Fields []*Filed // 字段属性组合
FieldNames []string // 字段名称
FieldMap map[string]*Filed // key:字段名 value:字段属性
}
代码说明:
- Field 结构体中包含5个成员变量,字段名称Name,所处位置i,对应数据库表列名类型Type,对应数据库表列名TableColunm,约束条件Tag。
- Schema 结构体中包含3个成员变量,字段属性集合Fields,字段名称集合FieldNames,FieldMap用于通过字段名称来获取字段相关信息。
接下来解说数据库表结构与对象之间的解析方式。
// raw.go
var (
structMutex sync.RWMutex
structCache = make(map[reflect.Type]*Schema)
)
// 对象与表结构转换
func StructForType(t reflect.Type) *Schema {
// step1: 缓存获取
structMutex.RLock()
st, found := structCache[t]
structMutex.RUnlock()
if found {
return st
}
structMutex.Lock()
defer structMutex.Unlock()
st, found = structCache[t]
if found {
return st
}
// step2: 对象关系映射
st = &Schema{FieldMap: make(map[string]*Filed)}
dataTypeOf(t, st)
// step3: 缓存
structCache[t] = st
return st
}
// 对象与表结构转换(实际工作函数)
func dataTypeOf(types reflect.Type, schema *Schema) {
// 遍历所有字段
for i := 0; i < types.NumField(); i++ {
p := types.Field(i)
// 忽略匿名字段和私有字段
if p.Anonymous || !ast.IsExported(p.Name) {
continue
}
field := &Filed{
Name: p.Name,
i: i,
}
var tag = field.Name
field.TableColumn = field.Name
if tg, ok := p.Tag.Lookup("torm"); ok {
tag = tg
}
// 获得额外约束条件
tagArr := strings.Split(tag, ",")
if len(tagArr) > 0 {
if tagArr[0] == "-" {
continue
}
// 数据库中对应列表名称
if len(tagArr[0]) > 0 {
field.TableColumn = tagArr[0]
}
// 数据库中对应列表类型
if len(tagArr) > 1 && len(tagArr[1]) > 0 {
field.Type = tagArr[1]
}
}
// 存储所有字段信息
schema.Fields = append(schema.Fields, field)
schema.FieldMap[p.Name] = field
// 存储所有字段名称
schema.FieldNames = append(schema.FieldNames, p.Name)
}
}
在 raw.go 文件中,就是进行对象与表结构之前的转换流程,其中最核心的就是 dataTypeOf 方法和 StructForType 方法,接下来会分别介绍这两个方法的实现思路:
-
dataTypeOf 方法实现思路
- 通过 type.NumField() 获取结构体中字段数量,进行循环遍历
- 若字段属性为匿名字段或者私有字段,则将其忽略
- 获取 Tag 标签中的信息,用逗号进行分割,第一位表示数据库列名(若为空,默认为字段名称);第二位表示数据库列名类型(若为空,默认为字段类型)
-
StructForType 方法实现思路
- 首先从缓存中进行获取字段信息,若不存在的话,则需进入dataTypeOf 方法中进行对象表结构映射,完成之后,进行缓存,便于后续再次使用时,提高性能
到这里, Schema 类的核心功能已经编写完成了,接下来进入测试阶段。
代码测试
// schema_test.go
package session
import (
log "github.com/sirupsen/logrus"
"reflect"
"testing"
)
type User struct {
Name string `torm:"user_name,varchar"`
Age int `torm:"age,int"`
}
func TestStructForType(t *testing.T) {
user := &User{}
utypes := reflect.TypeOf(user)
schema := StructForType(utypes.Elem())
log.Info(schema.FieldNames)
for i := 0; i < len(schema.Fields); i++ {
log.Info("字段名称:", schema.Fields[i].Name, ";字段类型:", schema.Fields[i].Type,
";对应数据库列名:", schema.Fields[i].TableColumn)
}
if len(schema.Fields) != 2 {
t.Fatal("failed to parse User struct")
}
if schema.FieldMap["Name"].Name != "Name" {
t.Fatal("failed to parse primary key")
}
}
结果:
=== RUN TestStructForType
time="2021-01-15T21:02:44+08:00" level=info msg="[Name Age]"
time="2021-01-15T21:02:44+08:00" level=info msg="字段名称:Name;字段类型:varchar;对应数据库列名:user_name"
time="2021-01-15T21:02:44+08:00" level=info msg="字段名称:Age;字段类型:int;对应数据库列名:age"
--- PASS: TestStructForType (0.00s)
PASS
代码目录
torm
|--raw.go // 底层与数据库交互语句
|--raw_test.go
|--schema.go // 对象表结构映射
|--schema_test.go
|--go.mod
到这里,第二天的任务也编写完成了,回顾一下,今天主要完成了对象表结构映射关系,将语言层面的对象转换成数据库表结构,进而与数据库进行交互。
文章也会持续更新,可以微信搜索「 迈莫coding 」第一时间阅读,「福利」、「博客」、「torm」获取福利,回复『1024』领取学习go资料。