項目地址: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等功能,並且會支持參數化查詢,請關注后續文章,希望能給個星星,謝謝~