项目地址:https://github.com/yoyofxteam/yoyodata

欢迎星星,感谢

前言:最近在学习Go语言,就出于学习目的手撸个小架子,欢迎提出宝贵意见,项目使用Mysql数据库进行开发

我们还使用Go遵循ASP.NET Core的设计理念开发出了对应的Web框架:https://github.com/yoyofxteam/yoyogo

遵循C#命名规范开发出的反射帮助类库:https://github.com/yoyofxteam/yoyo-reflect

欢迎Star

首先,我们来看一下在Go中如果我想查询出数据库的数据都需要干些什么

1.引入MySQL驱动github.com/go-sql-driver/mysql

2.执行查询,可以看到控制台输出了数据库内容,并像你发出了祖安问候



但是这个驱动的自带方法十分原始,我们需要自己创建与数据库类型一致的变量,然后取值在给字段赋值,十分麻烦,所以我们要动手把这步搞成自动化的

想实现自动装配就要解决三个问题:1.自动创建变量来获取数据库值;2.把接受到值赋值给结构体对象;3.把对象拼接成一个对象数组进行返回

因为rows.Scan()方法要求我们必须传入和查询sql中:字段顺序和数量以及类型必须一致的变量,才可以成功接受到返回值,所以我们必须按需创建变量进行绑定,具体设计见下文

1. 创建两个结构体分别用来保存结构体和结构体的字段信息


//类型缓存
type TypeInfo struct {
//类型名称
TypeName string
//类型下的字段
FieldInfo []FieldInfo
} //字段缓存
type FieldInfo struct {
//字段索引值
Index int
//字段名称
FieldName string
FieldValue reflect.Value
FieldType reflect.StructField
}

2.封装一个方法用于获取结构体的元数据,保存到我们上面定义的结构体中

func ReflectTypeInfo(model interface{}) cache.TypeInfo {
modelValue := reflect.ValueOf(model)
modelType := reflect.TypeOf(model)
//获取包名
pkg := modelType.PkgPath()
//获取完全限定类名
typeName := pkg + modelType.Name()
//判断对象的类型必须是结构体
if modelValue.Kind() != reflect.Struct {
panic("model must be struct !")
}
var fieldInfoArray []cache.FieldInfo
for i := 0; i < modelValue.NumField(); i++ {
fieldValue := modelValue.Field(i)
//如果字段是一个结构体则不进行元数据的获取
if fieldValue.Kind() == reflect.Struct {
continue
}
//按照索引获取字段
fieldType := modelType.Field(i)
fieldName := fieldType.Name
fieldInfoElement := cache.FieldInfo{
Index: i,
FieldName: fieldName,
FieldType: fieldType,
FieldValue: fieldValue,
}
fieldInfoArray = append(fieldInfoArray, fieldInfoElement)
}
typeInfo := cache.TypeInfo{
TypeName: typeName,
FieldInfo: fieldInfoArray,
}
return typeInfo
}

3.设计一个简单的缓存,把已经获取到元数据进行缓存避免重复获取

var TypeCache TypeInfoCache

type TypeInfoCache struct {
sync.RWMutex
Items map[string]TypeInfo
} //缓存初始化
func NewTypeInfoCache() { TypeCache = TypeInfoCache{
Items: make(map[string]TypeInfo),
}
} //获取缓存
func (c *TypeInfoCache) GetTypeInfoCache(key string) (TypeInfo, bool) {
c.RLock()
defer c.RUnlock()
value, ok := c.Items[key]
if ok {
return value, ok
}
return value, false
} //添加缓存
func (c *TypeInfoCache) SetTypeInfoCache(key string, typeInfo TypeInfo) {
c.RLock()
defer c.RUnlock()
c.Items[key] = typeInfo
} /**
从缓存中获取类型元数据信息
*/
func GetTypeInfo(model interface{}) cache.TypeInfo {
//使用 包名+结构体名作为缓存的Key
modelType := reflect.TypeOf(model)
typeName := modelType.PkgPath() + modelType.Name()
typeInfo, ok := cache.TypeCache.GetTypeInfoCache(typeName)
if ok {
return typeInfo
}
typeInfo = ReflectTypeInfo(model)
cache.TypeCache.SetTypeInfoCache(typeName, typeInfo)
return typeInfo
}

4.封装一个方法执行SQL语句并返回对应结构体的数组(划重点)

设计思路:

执行sql语句获取到返回的数据集

获取要装配的结构体的元数据

根据sql返回字段找到对应的结构体字段进行匹配

装配要返回的结构体对象

组装一个对象数据进行返回

package queryable

import (
"database/sql"
"github.com/yoyofxteam/yoyodata/cache"
"github.com/yoyofxteam/yoyodata/reflectx"
"reflect"
"sort"
"strings"
) type Queryable struct {
DB DbInfo
Model interface{}
} /**
执行不带参数化的SQL查询
*/
func (q *Queryable) Query(sql string, res interface{}) {
db, err := q.DB.CreateNewDbConn()
if err != nil {
panic(err)
}
rows, err := db.Query(sql)
if err != nil {
panic(err)
}
//获取返回值的原始数据类型
resElem := reflect.ValueOf(res).Elem()
if resElem.Kind() != reflect.Slice {
panic("value must be slice")
}
//获取对象完全限定名称和元数据
modelName := reflectx.GetTypeName(q.Model)
typeInfo := getTypeInfo(modelName, q.Model)
//获取数据库字段和类型字段的对应关系键值对
columnFieldSlice := contrastColumnField(rows, typeInfo)
//创建用于接受数据库返回值的字段变量对象
scanFieldArray := createScanFieldArray(columnFieldSlice)
resEleArray := make([]reflect.Value, 0)
//数据装配
for rows.Next() {
//创建对象
dataModel := reflect.New(reflect.ValueOf(q.Model).Type()).Interface()
//接受数据库返回值
rows.Scan(scanFieldArray...)
//为对象赋值
setValue(dataModel, scanFieldArray, columnFieldSlice)
resEleArray = append(resEleArray, reflect.ValueOf(dataModel).Elem())
}
//利用反射动态拼接切片
val := reflect.Append(resElem, resEleArray...)
resElem.Set(val)
//查询完毕后关闭链接
db.Close()
} /**
数据库字段和类型字段键值对
*/
type ColumnFieldKeyValue struct {
//SQL字段顺序索引
Index int
//数据库列名
ColumnName string
//数据库字段名
FieldInfo cache.FieldInfo
} /**
把数据库返回的值赋值到实体字段上
*/
func setValue(model interface{}, data []interface{}, columnFieldSlice []ColumnFieldKeyValue) {
modelVal := reflect.ValueOf(model).Elem()
for i, cf := range columnFieldSlice {
modelVal.Field(cf.FieldInfo.Index).Set(reflect.ValueOf(data[i]).Elem())
}
} /**
创建用于接受数据库数据的对应变量
*/
func createScanFieldArray(columnFieldSlice []ColumnFieldKeyValue) []interface{} {
var res []interface{}
for _, data := range columnFieldSlice {
res = append(res, reflect.New(data.FieldInfo.FieldValue.Type()).Interface())
}
return res
} /**
根据SQL查询语句中的字段找到结构体的对应字段,并且记录索引值,用于接下来根据索引值来进行对象的赋值
*/
func contrastColumnField(rows *sql.Rows, typeInfo cache.TypeInfo) []ColumnFieldKeyValue {
var columnFieldSlice []ColumnFieldKeyValue
columns, _ := rows.Columns()
for _, field := range typeInfo.FieldInfo {
for i, column := range columns {
if strings.ToUpper(column) == strings.ToUpper(field.FieldName) {
columnFieldSlice = append(columnFieldSlice, ColumnFieldKeyValue{ColumnName: column, Index: i, FieldInfo: field})
}
}
}
//把获取到的键值对按照SQL语句查询字段的顺序进行排序,否则会无法赋值
sort.SliceStable(columnFieldSlice, func(i, j int) bool {
return columnFieldSlice[i].Index < columnFieldSlice[j].Index
})
return columnFieldSlice
} /**
获取要查询的结构体的元数据,这个就是调用了一下第二部的那个方法
*/
func getTypeInfo(key string, model interface{}) cache.TypeInfo {
typeInfo, ok := cache.TypeCache.GetTypeInfoCache(key)
if !ok {
typeInfo = reflectx.GetTypeInfo(model)
}
return typeInfo
}

方法封装完毕,我们跑个单元测试看一下效果

目前这个小架子刚开始写,到发布这篇文档为止仅封装出了最基础的查询,接下来会实现Insert/Update等功能,并且会支持参数化查询,请关注后续文章,希望能给个星星,谢谢~

Go语言系列之手把手教你撸一个ORM(一)的更多相关文章

  1. 手把手教你撸一个 Webpack Loader

    文:小 boy(沪江网校Web前端工程师) 本文原创,转载请注明作者及出处 经常逛 webpack 官网的同学应该会很眼熟上面的图.正如它宣传的一样,webpack 能把左侧各种类型的文件(webpa ...

  2. C#多线程(16):手把手教你撸一个工作流

    目录 前言 节点 Then Parallel Schedule Delay 试用一下 顺序节点 并行任务 编写工作流 接口构建器 工作流构建器 依赖注入 实现工作流解析 前言 前面学习了很多多线程和任 ...

  3. WebRTC系列(1)-手把手教你实现一个浏览器拍照室Demo

    1.WebRTC开发背景 由于业务需求,需要在项目中实现实时音视频通话功能,之前基于浏览器开发的Web项目要进行音视频通话,需要安装flash插件才能实现或者使用C/S客户端进行通信.随着互联网技术的 ...

  4. Android应用系列:手把手教你做一个小米通讯录(附图附源码)

    前言 最近心血来潮,突然想搞点仿制品玩玩,很不幸小米成为我苦逼的第一个试验品.既然雷布斯的MIUI挺受欢迎的(本人就是其的屌丝用户),所以就拿其中的一些小功能做一些小demo来玩玩.小米的通讯录大家估 ...

  5. 手把手教你撸一个简易的 webpack

    背景 随着前端复杂度的不断提升,诞生出很多打包工具,比如最先的grunt,gulp.到后来的webpack和Parcel.但是目前很多脚手架工具,比如vue-cli已经帮我们集成了一些构建工具的使用. ...

  6. Vue手把手教你撸一个 beforeEnter 钩子函数

    地址 :https://www.jb51.net/article/138821.htm 地址 :https://www.jb51.net/article/108964.htm

  7. 学以致用:手把手教你撸一个工具库并打包发布,顺便解决JS浮点数计算精度问题

    本文讲解的是怎么实现一个工具库并打包发布到npm给大家使用.本文实现的工具是一个分数计算器,大家考虑如下情况: \[ \sqrt{(((\frac{1}{3}+3.5)*\frac{2}{9}-\fr ...

  8. 手把手教你撸个vue2.0弹窗组件

    手把手教你撸个vue2.0弹窗组件 在开始之前需要了解一下开发vue插件的前置知识,推荐先看一下vue官网的插件介绍 预览地址 http://haogewudi.me/kiko/inde... 源码地 ...

  9. 只有20行Javascript代码!手把手教你写一个页面模板引擎

    http://www.toobug.net/article/how_to_design_front_end_template_engine.html http://barretlee.com/webs ...

随机推荐

  1. JavaScript基础避免隐式的类型转换(004)

    JavaScript在普通对比运算符“==”执行时可能会进行隐式的类型转换.比如:false==0和""==0的结果都是true.同理也适合于"!="运算符.要 ...

  2. py之logging模块

    参考:https://www.cnblogs.com/yuanchenqi/articles/5732581.html 一 (简单应用) import logging logging.debug('d ...

  3. css使用rgba()或hsla()设置半透明或完全透明边框border

    在css中我们想实现透明颜色,首先就会想到rgba()和hsla()这2个属性.这篇文章就简单介绍下使用这2种方式来实现半透明边框. 1.使用rgba方式: border: 10px solid rg ...

  4. 弹性碰撞问题:Ants+Linear world

    题目一:Ants 传送门 题目描述 输入 输出 样例 样例输入 样例输出 分析 一句话题意:有n只蚂蚁在木棍上爬行,每只蚂蚁的速度都是每秒1单位长度,现在给你所有蚂蚁初始的位置(蚂蚁运动方向未定),蚂 ...

  5. Javascript安全编程规范

    命名 1.原则——编码格式UTF-8,根据功能为变量与方法命名(尽量不缩写) 2.类——构造函数/类 使用驼峰命名法,且首字母大写 3.方法——动词.动宾结构,使用驼峰命名法,首字母小写 例如:get ...

  6. flex-direction和flex-wrap

    当外层容器使用flex布局,并且把flex-direction设置成colum的时候,内层容器的宽度会跟外层容器的宽度保持一致. 在浏览器上的效果如下: 当把外层容器的纵向布局不适用flex-dire ...

  7. Django---进阶13

    目录 数据库表创建及同步 注册功能 登陆功能 bbs是一个前后端不分离的全栈项目,前端和后端都需要我们自己一步步的完成 表创建及同步 注册功能 forms组件 用户头像前端实时展示 ajax 登陆功能 ...

  8. 使用virtualBox 创建虚拟机

    第一次使用感觉并没有VMware好用,尤其是鼠标在虚拟机和宿主机之间切换的时候很烦,需要按键盘右边ctrl虽然有提示right ctrl但是第一次使用硬是折腾了好半天.感觉不记录一下对不起这个下午. ...

  9. day30 继承、派生与多态,类中方法和内置函数

    目录 一.多继承出现的问题(mixins机制) 二.派生与方法重用 三.多态 1 什么是多态 2 为什么要有多态 3 python中多态的鸭子类型 四.绑定方法与非绑定方法 1 绑定方法 1.1对象的 ...

  10. day22 作业

    1 统计文件夹大小 def get_filesize(file_path): count = 0 res=None files_sum = os.listdir(file_path) for file ...