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:1234567@tcp(127.0.0.1:3306)/hello")
if err != nil {
log.Fatal(err)
}
defer db.Close()
var (
id int
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:1234567@tcp(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 { Value interface{} Error error RowsAffected int64 // single db db SQLCommon blockGlobalUpdate bool logMode logModeValue logger logger search *search values sync.Map // global db parent *DB callbacks *Callback dialect Dialect 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{} selects map[string]interface{} omits []string orders []interface{} preload []searchPreload offset interface{} limit interface{} group string tableName string raw bool Unscoped bool ignoreOrderQuery bool }
gorm.Scope
用来保存当前操作的各种信息// Scope contain current operation's information when you perform any operation on the database type Scope struct { Search *search Value interface{} SQL string SQLVars []interface{} db *DB instanceID string primaryKeyField *Field skipLeft bool 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的定义。
更多文章
本站热门
- socks5 协议详解
- zerotier简明教程
- 搞定面试中的系统设计题
- frp 源码阅读与分析(一):流程和概念
- 用peewee代替SQLAlchemy
- Golang(Go语言)中实现典型的fork调用
- DNSCrypt简明教程
- 一个Gunicorn worker数量引发的血案
- Golang validator使用教程
- Docker组件介绍(一):runc和containerd
- Docker组件介绍(二):shim, docker-init和docker-proxy
- 使用Go语言实现一个异步任务框架
- 协程(coroutine)简介 - 什么是协程?
- SQLAlchemy简明教程
- Go Module 简明教程