GORM源码阅读与分析
最近读了一下GORM的源码,以下是简述。我们将以一个查询来看GORM是怎么调用的。
事前准备
新建一个数据库,建一张表,并且插入一行数据:
mysql root@127.0.0.1:(none)> create database hello default charset utf8mb4;
Query OK, 1 row affected
Time: 0.002s
mysql root@127.0.0.1:(none)> use hello
You are now connected to database "hello" as user "root"
Time: 0.001s
mysql root@127.0.0.1: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 root@127.0.0.1:hello> show tables;
+-----------------+
| Tables_in_hello |
+-----------------+
| users |
+-----------------+
1 row in set
Time: 0.013s
mysql root@127.0.0.1:hello> insert into users(id, name) values (1, 'me');
Query OK, 1 row affected
Time: 0.004s
mysql root@127.0.0.1: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简明教程
- Docker组件介绍(一):runc和containerd
- Docker组件介绍(二):shim, docker-init和docker-proxy
- Golang(Go语言)中实现典型的fork调用
- Go Module 简明教程
- DNSCrypt简明教程
- frp 源码阅读与分析(一):流程和概念
- SQLAlchemy简明教程
- 协程(coroutine)简介 - 什么是协程?
- Golang validator使用教程
- 使用Go语言实现一个异步任务框架
- Golang的template(模板引擎)简明教程
- 使用Gitea+Drone打造自己的CI/CD系统