GORM原始碼閱讀
最近讀了一下GORM的原始碼,以下是簡述。我們將以一個查詢來看GORM是怎麼呼叫的。
事前準備
新建一個數據庫,建一張表,並且插入一行資料:
mysql [email protected]:(none)> create database hello default charset utf8mb4; Query OK, 1 row affected Time: 0.002s mysql [email protected]:(none)> use hello You are now connected to database "hello" as user "root" Time: 0.001s mysql [email protected]:hello> CREATE TABLE `users` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `created_at` timestamp NULL DEFAULT NULL, `updated_at` timestamp NULL DEFAULT NULL, `deleted_at` timestamp NULL DEFAULT NULL, `name` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`), KEY `idx_resources_deleted_at` (`deleted_at`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; Query OK, 0 rows affected Time: 0.020s mysql [email protected]:hello> show tables; +-----------------+ | Tables_in_hello | +-----------------+ | users| +-----------------+ 1 row in set Time: 0.013s mysql [email protected]:hello> insert into users(id, name) values (1, 'me'); Query OK, 1 row affected Time: 0.004s mysql [email protected]:hello> select * from users; +----+------------+------------+------------+------+ | id | created_at | updated_at | deleted_at | name | +----+------------+------------+------------+------+ | 1| <null>| <null>| <null>| me| +----+------------+------------+------------+------+ 1 row in set Time: 0.016s
先看database/sql
GORM本身是構建在database/sql
之上的。我們先看看典型的database/sql
應該咋用:
package main import ( "database/sql" "log" _ "github.com/go-sql-driver/mysql" ) func main() { db, err := sql.Open("mysql", "root:[email protected](127.0.0.1:3306)/hello") if err != nil { log.Fatal(err) } defer db.Close() var ( idint name string ) rows, err := db.Query("select id, name from users where id = ?", 1) if err != nil { log.Fatal(err) } defer rows.Close() for rows.Next() { err := rows.Scan(&id, &name) if err != nil { log.Fatal(err) } log.Println(id, name) } err = rows.Err() if err != nil { log.Fatal(err) } }
看得出來,關鍵就在於,第一,db.Query
,第二,rows.Scan
,而剛好GORM是構建在database/sql
之上的,所以GORM要做的事情
就是:
- 通過struct tags來生成資料庫model
- 當查詢時,通過反射把資料查詢到對應的此前定義的model
檢視GORM程式碼
我們看看上面的例子,GORM該怎麼寫:
package main import ( "log" "github.com/jinzhu/gorm" _ "github.com/jinzhu/gorm/dialects/mysql" ) type User struct { gorm.Model Name string } func main() { db, err := gorm.Open("mysql", "root:[email protected](127.0.0.1:3306)/hello") if err != nil { panic("failed to connect database") } defer db.Close() var user User err = db.Where("id = ?", 1).First(&user).Error log.Printf("user: %+v, error: %s", user, err) }
可以看出來,GORM使用了鏈式API。
GORM有三個重要的組成結構:
-
gorm.DB
用來儲存當前DB連線的各種資訊
// DB contains information for current db connection type DB struct { Valueinterface{} Errorerror RowsAffected int64 // single db dbSQLCommon blockGlobalUpdate bool logModelogModeValue loggerlogger search*search valuessync.Map // global db parent*DB callbacks*Callback dialectDialect singularTable bool }
-
gorm.search
用來儲存各種查詢條件
type search struct { db*DB whereConditions[]map[string]interface{} orConditions[]map[string]interface{} notConditions[]map[string]interface{} havingConditions []map[string]interface{} joinConditions[]map[string]interface{} initAttrs[]interface{} assignAttrs[]interface{} selectsmap[string]interface{} omits[]string orders[]interface{} preload[]searchPreload offsetinterface{} limitinterface{} groupstring tableNamestring rawbool Unscopedbool ignoreOrderQuery bool }
-
gorm.Scope
用來儲存當前操作的各種資訊
// Scope contain current operation's information when you perform any operation on the database type Scope struct { Search*search Valueinterface{} SQLstring SQLVars[]interface{} db*DB instanceIDstring primaryKeyField *Field skipLeftbool fields*[]*Field selectAttrs*[]string }
我們來看看上面的db.Where("id = ?", 1).First(&user)
的程式碼呼叫:
-
db.Where("id = ?", 1)
這裡,就是呼叫這段程式碼:
func (s *DB) Where(query interface{}, args ...interface{}) *DB { return s.clone().search.Where(query, args...).db } func (s *DB) clone() *DB { db := &DB{ db:s.db, parent:s.parent, logger:s.logger, logMode:s.logMode, values:map[string]interface{}{}, Value:s.Value, Error:s.Error, blockGlobalUpdate: s.blockGlobalUpdate, } for key, value := range s.values { db.values[key] = value } if s.search == nil { db.search = &search{limit: -1, offset: -1} } else { db.search = s.search.clone() } db.search.db = db return db } func (s *search) Where(query interface{}, values ...interface{}) *search { s.whereConditions = append(s.whereConditions, map[string]interface{}{"query": query, "args": values}) return s }
可以看到,其實就是增加了查詢條件。
-
.First(&user)
就是:
func (s *DB) First(out interface{}, where ...interface{}) *DB { newScope := s.NewScope(out) newScope.Search.Limit(1) return newScope.Set("gorm:order_by_primary_key", "ASC"). inlineCondition(where...).callCallbacks(s.parent.callbacks.queries).db } // queryCallback used to query data from database func queryCallback(scope *Scope) { // ... 省略 scope.prepareQuerySQL() if !scope.HasError() { scope.db.RowsAffected = 0 if str, ok := scope.Get("gorm:query_option"); ok { scope.SQL += addExtraSpaceIfExist(fmt.Sprint(str)) } if rows, err := scope.SQLDB().Query(scope.SQL, scope.SQLVars...); scope.Err(err) == nil { defer rows.Close() columns, _ := rows.Columns() for rows.Next() { scope.db.RowsAffected++ elem := results if isSlice { elem = reflect.New(resultType).Elem() } scope.scan(rows, columns, scope.New(elem.Addr().Interface()).Fields()) if isSlice { if isPtr { results.Set(reflect.Append(results, elem.Addr())) } else { results.Set(reflect.Append(results, elem)) } } } if err := rows.Err(); err != nil { scope.Err(err) } else if scope.db.RowsAffected == 0 && !isSlice { scope.Err(ErrRecordNotFound) } } } }
可以看到,就是最後呼叫了DefaultCallback
裡的queryCallback
,而queryCallback
其實就是我們最上面說的,GORM該做的事情。
並且可以看出來,執行.First()
的時候,新建了一個Scope
,這也符合scope的定義。