Golang實現(xiàn)自己的orm框架實例探索
引言
本項目相當于gorm的精簡版,通過學習本項目可以更好的理解orm框架每個函數(shù)都在做什么,以及為什么這么定義框架功能。
通過本項目學到什么?
- 定義自己的日志庫logger
- 如何將結構體轉化成數(shù)據(jù)庫的表信息(字段/字段類型/表名)
- 如何實現(xiàn)常用的CRUD?
- 如何實現(xiàn)事務?
- 如何實現(xiàn)鉤子?
Golang orm框架實現(xiàn)
該項目的核心數(shù)據(jù)結構為 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: 負責執(zhí)行sql語句
tx: 通過事務執(zhí)行sql語句
tableMeta: Golang的結構體轉成的 表信息
clause: sql語句構造器(負責生成CRUD SQL 字符串 )
dial: 將Golang數(shù)據(jù)類型轉成 數(shù)據(jù)庫的類型
sql/sqlParam: 待執(zhí)行的sql和參數(shù)
定義自己的日志庫logger
特色:就是不同的日志級別有不同的顏色

如何將結構體轉化成數(shù)據(jù)庫的表信息(字段/字段類型/表名)
// Parse 將結構體轉化成 表/字段/字段類型
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()) // 取出類型,構造新對象
}
meta := &TableMeta{
Model: value.Interface(), // 保存對象
fieldMap: make(map[string]*Field),
}
destValue := reflect.Indirect(value)
destType := destValue.Type()
// 結構體類型名:就是表名
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)
// 非匿名 && 可導出
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
}如何實現(xiàn)常用的CRUD
這里以新增為例:一般的調(diào)用方式為 s.Insert(&User{},&User{})
整個代碼其實就是基于傳入的values,通過 reflect的相關函數(shù)提取出其中的字段值,最終的目的在于拼接成 insert into $table ($field) values (?,?),(?,?) 標準的SQL語句,傳給底層的驅(qū)動執(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:負責生成 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)
// 提取結構體中對象字段的值,作為sql語言的參數(shù)
valueSlice = append(valueSlice, s.TableMeta().ExtractFieldValue(value))
}
// clause values:負責生成 values (?,?,?),(?,?,?)
s.clause.Set(clause.VALUES, valueSlice...)
//s.clause.Build: 負責將 insert into $tableName ($FieldName) 拼接上 values (?,?,?),(?,?,?) 形成完整的sql語句
sql, sqlParam := s.clause.Build(clause.INSERT, clause.VALUES)
// exec sql : 實際執(zhí)行sql
result, err := s.Raw(sql, sqlParam...).Exec()
if err != nil {
return0, err
}
s.CallMethod(AfterInsert, nil)
return result.RowsAffected()
}
如何實現(xiàn)事務?
通過Session的Begin方法開啟事務,在回調(diào)函數(shù) f TxFunc中進行具體的【數(shù)據(jù)庫業(yè)務邏輯】,最后基于 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)
}如何實現(xiàn)鉤子?
其實就是利用reflect.MethodByName,讀取 value 對象的函數(shù),然后Call執(zhí)行該函數(shù),本質(zhì)就是回調(diào)。利用鉤子在獲取到數(shù)據(jù)以后,用戶可以在回調(diào)中對數(shù)據(jù)進行統(tǒng)一修改,對執(zhí)行結果進行收口
// 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)
}
}
}
}項目拖過地址: https://github.com/gofish2020/easyorm
以上就是Golang實現(xiàn)自己的orm框架實例探索的詳細內(nèi)容,更多關于Golang orm框架的資料請關注腳本之家其它相關文章!
相關文章
基于Go?goroutine實現(xiàn)一個簡單的聊天服務
對于聊天服務,想必大家都不會陌生,因為在我們的生活中經(jīng)常會用到,本文我們用?Go?并發(fā)來實現(xiàn)一個聊天服務器,這個程序可以讓一些用戶通過服務器向其它所有用戶廣播文本消息,文中通過代碼示例介紹的非常詳細,需要的朋友可以參考下2023-06-06
go?module化?import?調(diào)用本地模塊?tidy的方法
這篇文章主要介紹了go?module化?import?調(diào)用本地模塊?tidy的相關知識,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-09-09
一文教你如何快速學會Go的struct數(shù)據(jù)類型
結構是表示字段集合的用戶定義類型。它可以用于將數(shù)據(jù)分組為單個單元而不是將每個數(shù)據(jù)作為單獨的值的地方。本文就來和大家聊聊Go中struct數(shù)據(jù)類型的使用,需要的可以參考一下2023-03-03

