gorm查詢流程原始碼分析
gorm查詢流程原始碼分析
gorm是用golang寫的資料庫orm庫,目前golang寫的orm庫也有很多,例如xorm,beego orm,gomybatis等,各有各的優勢特點,看一下gorm對golang基礎框架中資料庫相關介面是如何封裝的。
gorm一般的初始化方式
db, err := gorm.Open("mysql", "user:password@/dbname?charset=utf8&parseTime=True&loc=Local") if err != nil { log.Errorf("init error!") }
gorm中DB結構體的定義:
// DB的結構體 type DB struct { sync.RWMutex// 鎖 Valueinterface{}// 一般傳入實際操作的表所對應的結構體 Errorerror// DB操作失敗的error RowsAffected int64// 操作影響的行數 // single db dbSQLCommon// SQL介面,包括(Exec、Prepare、Query、QueryRow) blockGlobalUpdate bool// 為true時,可以在update在沒有where條件是報錯,避免全域性更新 logModelogModeValue// 日誌模式,gorm提供了三種 loggerlogger// 內部日誌例項 search*search// 查詢相關的條件 valuessync.Map// value Map // global db parent*DB// 父db,為了儲存一個空的初始化後的db,也為了儲存curd註冊的的callback方法 callbacks*Callback// callback方法 dialectDialect// 不同型別資料庫對應的不同實現的相同介面 singularTable bool// 表名是否為複數形式,true時為user,false時為users }
gorm的Open方法:
func Open(dialect string, args ...interface{}) (db *DB, err error) { if len(args) == 0 { err = errors.New("invalid database source") return nil, err } var source string var dbSQL SQLCommon var ownDbSQL bool switch value := args[0].(type) { case string: var driver = dialect if len(args) == 1 { source = value } else if len(args) >= 2 { driver = value source = args[1].(string) } // 呼叫go基礎庫的Open方法獲得db的connention附給dbSQL, // 此時還沒有真正連線資料庫 dbSQL, err = sql.Open(driver, source) ownDbSQL = true case SQLCommon: dbSQL = value ownDbSQL = false default: return nil, fmt.Errorf("invalid database source: %v is not a valid type", value) } // 初始化DB db = &DB{ db:dbSQL, logger:defaultLogger, callbacks: DefaultCallback, dialect:newDialect(dialect, dbSQL), } // 將初始化的DB儲存到db.parent中 db.parent = db if err != nil { return } // 呼叫go基礎庫的Ping方法檢測資料庫connention是否可以連通 if d, ok := dbSQL.(*sql.DB); ok { if err = d.Ping(); err != nil && ownDbSQL { d.Close() } } return }
gorm是通過多個callbsck方法來實現curd的,具體流程以一個查詢為例:
DBEngine.Table(entry.TableName). Select(entry.Select). Where(entry.sql, entry.values). Order(entry.order). Find(entry.result)
執行步驟:
1.執行Table方法,新增tablename條件:
func (s *DB) Table(name string) *DB { clone := s.clone()// 執行clone方法也就是從新的db中賦值一個空的,避免交叉影響 clone.search.Table(name)// 賦值table name clone.Value = nil// 附空 return clone }
2.執行Where方法,新增where條件:
// 首先也是呼叫clone方法,然後呼叫search的Where方法 func (s *DB) Where(query interface{}, args ...interface{}) *DB { return s.clone().search.Where(query, args...).db } // search的Where方法是將傳進來的條件進行拼接,存入search.whereConditions func (s *search) Where(query interface{}, values ...interface{}) *search { s.whereConditions = append(s.whereConditions, map[string]interface{}{"query": query, "args": values}) return s }
3.執行Order方法,新增order條件:
// 類似Where,reorder為true會強制刷掉gorm預設的order by func (s *DB) Order(value interface{}, reorder ...bool) *DB { return s.clone().search.Order(value, reorder...).db } func (s *search) Order(value interface{}, reorder ...bool) *search { // 如果為true,先清除s.orders if len(reorder) > 0 && reorder[0] { s.orders = []interface{}{} } // 將value拼接,存入s.orders if value != nil && value != "" { s.orders = append(s.orders, value) } return s }
4.執行Find方法,真正實現查詢:
// 首先先建立一個scope(可以理解成只針對本次資料庫操作有效的一個環境),再呼叫inlineCondition內部方法,最後執行callcallbacks一系列方法實現真正的查詢操作,並將db返回 func (s *DB) Find(out interface{}, where ...interface{}) *DB { return s.NewScope(out).inlineCondition(where...).callCallbacks(s.parent.callbacks.queries).db } // NewScope方法就是初始化一個scope func (s *DB) NewScope(value interface{}) *Scope { dbClone := s.clone() // 此時賦值value dbClone.Value = value scope := &Scope{db: dbClone, Value: value} if s.search != nil { scope.Search = s.search.clone() } else { scope.Search = &search{} } return scope } // inlineCondition方法是執行scope.Search.Where func (scope *Scope) inlineCondition(values ...interface{}) *Scope { if len(values) > 0 { scope.Search.Where(values[0], values[1:]...) } return scope } // scope.Search.Where實際上也是執行條件拼接,由於我們在呼叫的時候沒有在Find中傳入條件,所以這個方法不會被執行 func (s *search) Where(query interface{}, values ...interface{}) *search { s.whereConditions = append(s.whereConditions, map[string]interface{}{"query": query, "args": values}) return s } // 最重要的就是callcallbacks方法,是真正執行的地方 func (scope *Scope) callCallbacks(funcs []*func(s *Scope)) *Scope { defer func() { if err := recover(); err != nil { if db, ok := scope.db.db.(sqlTx); ok { db.Rollback() } panic(err) } }() // 迴圈裡面所有的註冊的funcs for _, f := range funcs { (*f)(scope) if scope.skipLeft { break } } return scope } // 這裡的funcs實在程式啟動時init方法註冊的 func init() { DefaultCallback.Query().Register("gorm:query", queryCallback) DefaultCallback.Query().Register("gorm:preload", preloadCallback) DefaultCallback.Query().Register("gorm:after_query", afterQueryCallback) } // 比如afterQueryCallback方法還提供了反射呼叫結構體的AfterFind方法,如果在查詢前結構體實現了AfterFind方法就會被呼叫,這個機制比了靈活 func afterQueryCallback(scope *Scope) { if !scope.HasError() { scope.CallMethod("AfterFind") } }
Find方法主要的執行流程就是這樣,還有些詳細的後續再補充,寫的不對的希望給指出更正