Golang實(shí)現(xiàn)自己的orm框架實(shí)例探索
引言
本項(xiàng)目相當(dāng)于gorm的精簡(jiǎn)版,通過學(xué)習(xí)本項(xiàng)目可以更好的理解orm框架每個(gè)函數(shù)都在做什么,以及為什么這么定義框架功能。
通過本項(xiàng)目學(xué)到什么?
- 定義自己的日志庫(kù)logger
- 如何將結(jié)構(gòu)體轉(zhuǎn)化成數(shù)據(jù)庫(kù)的表信息(字段/字段類型/表名)
- 如何實(shí)現(xiàn)常用的CRUD?
- 如何實(shí)現(xiàn)事務(wù)?
- 如何實(shí)現(xiàn)鉤子?
Golang orm框架實(shí)現(xiàn)
該項(xiàng)目的核心數(shù)據(jù)結(jié)構(gòu)為 Session
type Session struct { db *sql.DB tx *sql.Tx // table tableMeta *table.TableMeta dial dialect.Dialect // clause clause clause.Clause // sql query sql strings.Builder // sql param sqlParam []interface{} }
db: 負(fù)責(zé)執(zhí)行sql語(yǔ)句
tx: 通過事務(wù)執(zhí)行sql語(yǔ)句
tableMeta: Golang的結(jié)構(gòu)體轉(zhuǎn)成的 表信息
clause: sql語(yǔ)句構(gòu)造器(負(fù)責(zé)生成CRUD SQL 字符串 )
dial: 將Golang數(shù)據(jù)類型轉(zhuǎn)成 數(shù)據(jù)庫(kù)的類型
sql/sqlParam: 待執(zhí)行的sql和參數(shù)
定義自己的日志庫(kù)logger
特色:就是不同的日志級(jí)別有不同的顏色
如何將結(jié)構(gòu)體轉(zhuǎn)化成數(shù)據(jù)庫(kù)的表信息(字段/字段類型/表名)
// Parse 將結(jié)構(gòu)體轉(zhuǎn)化成 表/字段/字段類型 func Parse(dest interface{}, d dialect.Dialect) *TableMeta { // 是否為nil if dest == nil { returnnil } // (*User)(nil) value := reflect.ValueOf(dest) if value.Kind() == reflect.Ptr && value.IsNil() { value = reflect.New(value.Type().Elem()) // 取出類型,構(gòu)造新對(duì)象 } meta := &TableMeta{ Model: value.Interface(), // 保存對(duì)象 fieldMap: make(map[string]*Field), } destValue := reflect.Indirect(value) destType := destValue.Type() // 結(jié)構(gòu)體類型名:就是表名 m, ok := value.Interface().(Tabler) if ok { meta.TableName = m.TableName() } else { meta.TableName = destType.Name() } for i := 0; i < destType.NumField(); i++ { fieldType := destType.Field(i) // 非匿名 && 可導(dǎo)出 if !fieldType.Anonymous && fieldType.IsExported() { // 成員變量:就是字段名 // 成員變量類型:就是字段類型 field := &Field{ FieldName: fieldType.Name, FieldType: d.ConvertType2DBType(destValue.Field(i)), FieldTag: fieldType.Tag.Get("easyorm"), } meta.Fields = append(meta.Fields, field) meta.FieldsName = append(meta.FieldsName, field.FieldName) meta.fieldMap[field.FieldName] = field } } return meta }
如何實(shí)現(xiàn)常用的CRUD
這里以新增為例:一般的調(diào)用方式為 s.Insert(&User{},&User{})
整個(gè)代碼其實(shí)就是基于傳入的values,通過 reflect
的相關(guān)函數(shù)提取出其中的字段值,最終的目的在于拼接成 insert into $table ($field) values (?,?),(?,?)
標(biāo)準(zhǔn)的SQL語(yǔ)句,傳給底層的驅(qū)動(dòng)執(zhí)行
// s.Insert(&User{},&User{}) func (s *Session) Insert(values ...interface{}) (int64, error) { iflen(values) == 0 { return0, errors.New("param is empty") } // init table s.Model(values[0]) // clause insert:負(fù)責(zé)生成 insert into $tableName ($FieldName) 字符串 s.clause.Set(clause.INSERT, s.TableMeta().TableName, s.TableMeta().FieldsName) valueSlice := []interface{}{} for _, value := range values { s.CallMethod(BeforeInsert, value) // 提取結(jié)構(gòu)體中對(duì)象字段的值,作為sql語(yǔ)言的參數(shù) valueSlice = append(valueSlice, s.TableMeta().ExtractFieldValue(value)) } // clause values:負(fù)責(zé)生成 values (?,?,?),(?,?,?) s.clause.Set(clause.VALUES, valueSlice...) //s.clause.Build: 負(fù)責(zé)將 insert into $tableName ($FieldName) 拼接上 values (?,?,?),(?,?,?) 形成完整的sql語(yǔ)句 sql, sqlParam := s.clause.Build(clause.INSERT, clause.VALUES) // exec sql : 實(shí)際執(zhí)行sql result, err := s.Raw(sql, sqlParam...).Exec() if err != nil { return0, err } s.CallMethod(AfterInsert, nil) return result.RowsAffected() }
如何實(shí)現(xiàn)事務(wù)?
通過Session
的Begin
方法開啟事務(wù),在回調(diào)函數(shù) f TxFunc
中進(jìn)行具體的【數(shù)據(jù)庫(kù)業(yè)務(wù)邏輯】,最后基于 f TxFunc
是否執(zhí)行返回 error
來決定是Commit
還是 Rollback
// TxFunc will be called between tx.Begin() and tx.Commit() type TxFunc func(*session.Session) (interface{}, error) // Transaction executes sql wrapped in a transaction, then automatically commit if no error occurs func (engine *Engine) Transaction(f TxFunc) (result interface{}, err error) { s := engine.NewSession() if err := s.Begin(); err != nil { returnnil, err } deferfunc() { if p := recover(); p != nil { _ = s.Rollback() panic(p) // re-throw panic after Rollback } elseif err != nil { _ = s.Rollback() // err is non-nil; don't change it } else { err = s.Commit() // err is nil; if Commit returns error update err } }() return f(s) }
如何實(shí)現(xiàn)鉤子?
其實(shí)就是利用reflect.MethodByName
,讀取 value 對(duì)象的函數(shù),然后Call
執(zhí)行該函數(shù),本質(zhì)就是回調(diào)。利用鉤子在獲取到數(shù)據(jù)以后,用戶可以在回調(diào)中對(duì)數(shù)據(jù)進(jìn)行統(tǒng)一修改,對(duì)執(zhí)行結(jié)果進(jìn)行收口
// CallMethod calls the registered hooks func (s *Session) CallMethod(method string, value interface{}) { fm := reflect.ValueOf(s.TableMeta().Model).MethodByName(method) if value != nil { fm = reflect.ValueOf(value).MethodByName(method) } param := []reflect.Value{reflect.ValueOf(s)} if fm.IsValid() { if v := fm.Call(param); len(v) > 0 { if err, ok := v[0].Interface().(error); ok { logger.Error(err) } } } }
項(xiàng)目拖過地址: https://github.com/gofish2020/easyorm
以上就是Golang實(shí)現(xiàn)自己的orm框架實(shí)例探索的詳細(xì)內(nèi)容,更多關(guān)于Golang orm框架的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
基于Go?goroutine實(shí)現(xiàn)一個(gè)簡(jiǎn)單的聊天服務(wù)
對(duì)于聊天服務(wù),想必大家都不會(huì)陌生,因?yàn)樵谖覀兊纳钪薪?jīng)常會(huì)用到,本文我們用?Go?并發(fā)來實(shí)現(xiàn)一個(gè)聊天服務(wù)器,這個(gè)程序可以讓一些用戶通過服務(wù)器向其它所有用戶廣播文本消息,文中通過代碼示例介紹的非常詳細(xì),需要的朋友可以參考下2023-06-06go語(yǔ)言K8S?的?informer機(jī)制淺析
這篇文章為大家主要介紹了go語(yǔ)言K8S?的?informer機(jī)制淺析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10go?module化?import?調(diào)用本地模塊?tidy的方法
這篇文章主要介紹了go?module化?import?調(diào)用本地模塊?tidy的相關(guān)知識(shí),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-09-09一文教你如何快速學(xué)會(huì)Go的struct數(shù)據(jù)類型
結(jié)構(gòu)是表示字段集合的用戶定義類型。它可以用于將數(shù)據(jù)分組為單個(gè)單元而不是將每個(gè)數(shù)據(jù)作為單獨(dú)的值的地方。本文就來和大家聊聊Go中struct數(shù)據(jù)類型的使用,需要的可以參考一下2023-03-03sublime3+Golang+代碼補(bǔ)全的實(shí)現(xiàn)
本文主要介紹了sublime3+Golang+代碼補(bǔ)全的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-01-01