项目地址: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. 2020/6/10 JavaScript高级程序设计 BOM

    BOM(浏览器对象模型):提供用于访问浏览器的对象. 8.1 window对象 window是BOM的核心对象,表示浏览器的一个实例. JavaScript访问浏览器窗口的接口 ECMAScript规 ...

  2. HTTP之User-Agent大全

    User-Agent 首部包含了一个特征字符串,用来让网络协议的对端来识别发起请求的用户代理软件的应用类型.操作系统.软件开发商以及版本号. 以下是一些常见的各种浏览器的User-Agent: 1) ...

  3. 46道Linux面试题送给你(后续会不断更新)

    绝对路径用什么符号表示? 当前目录.上层目录用什么表示?主目录用什么表示? 切换目录用什么命令? 答案: # 绝对路径: 如/etc/init.d # 当前目录和上层目录: ./ ../ # 主目录: ...

  4. Android/iOS内嵌Unity开发示例

    Unity 与 Android/iOS 交叉开发主要有两种方式,以 Android 为例,一是 Android 生成 jar 或者 aar 包,导入到 unity3d plugin/bin/ 目录下: ...

  5. Jenkins - 解决集成 jmeter+ant 发送邮件时报错:java.lang.ClassNotFoundException: javax.mail.internet.MimeMessage

    jenkins + jmeter +ant 发送邮件失败 问题原因 其实就是缺失 jar 包,导致某些类找不到了 解决方案 点击该网站,下载commons-email.jar包 点击该网站,下载act ...

  6. 浏览器如何解析css选择器?

    浏览器会『从右往左』解析CSS选择器. 我们知道DOM Tree与Style Rules合成为 Render Tree,实际上是需要将Style Rules附着到DOM Tree上, 因此需要根据选择 ...

  7. 武汉中科通达软件Java工程师初试总结复盘

       预约的视频面试时间是中午12点,不过面试官并没有准时到,拖了大概5.6分钟吧.Zoom会议上写着xxxJava工程师初试. 面试官戴着口罩,并没有露脸,看起来与我年龄相仿,感觉很年轻. 在我按着 ...

  8. Zookeeper是什么&怎么用

    1.Zookeeper概述 Zookeeper 是一个开源的分布式协调服务框架 ,主要用来解决分布式集群中应用系统的一致性问题和数据管理问题 2:Zookeeper的特点 Zookeeper 本质上是 ...

  9. spring-boot 应用 报错 No qualifying bean of type XXXXX.***Mapper

    报错类型 NoSuchBeanDefinitionException.No qualifying bean of type  XXXXX.***Mapper 报错信息详情 Caused by: org ...

  10. 数据库04 /多表查询、pymysql模块

    数据库04 /多表查询.pymysql模块 目录 数据库04 /多表查询.pymysql模块 1. 笛卡尔积 2. 连表查询 2.1 inner join 内连接 2.2 left join 左连接 ...