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 (
        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:[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 {
        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的定义。



更多文章