【七天从零实现ORM|Day03:对象表结构映射】学完吊打面试官

在这里插入图片描述

今天是【7天从零实现TORM框架】的第二天,主要任务是:

  • 使用反射(reflect)获取struct结构体中的字段属性,将其映射到数据库中的表信息。代码100行左右。

若对Go中反射的使用不了解的话,我写了三篇关于反射的文章,给小伙伴提供参考,足以应对本项目中所使用的反射知识点。

源代码:在【迈莫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资料。

猜你喜欢

转载自blog.csdn.net/qq_41066066/article/details/112920506