學習 gorm:gorm 中的核心數據結構

大家好,我是漁夫子。

今天咱們一起來學習下 gorm 中的幾個核心數據結構。通過了解 gorm 底層的數據結構,能夠讓我們瞭解 gorm 底層的實現,以便更好的使用 gorm。

在 gorm 中主要有 5 個核心結構:DB、Config、Statment、Clause 和 Schema。接下來我們就詳細的看下每種數據結構以及各結構之間的關係。

一、DB

在使用 gorm 的時候,我們首先會使用 gorm.Open 方法和數據庫建立連接,同時並返回一個 gorm.DB 結構。如下:

var db *gorm.DB
dsn := "username:password@tcp(127.0.0.1:3306)/test01?charset=utf8mb4&parseTime=True&loc=Local&timeout=1000ms"

config := &gorm.Config{
 NamingStrategy: schema.NamingStrategy{
     SingularTable: true, // 禁用表名複數
 },
}

db, _ := gorm.Open(mysql.Open(dsn), config)

如上,db 變量就是 * gorm.DB 類型的,如下:

// DB GORM DB definition
type DB struct {
 *Config
 Error        error
 RowsAffected int64
 Statement    *Statement
 clone        int
}

大家看,DB 結構裏包含了嵌入的結構 * Config,還有一個 Statement 結構。

二、Config 結構

Config 結構是包含在 DB 結構內的。顧名思義,Config 就是和數據庫相關的一些配置。在 gorm.Open 函數中傳入的,如上面我們對數據表命名的配置中禁用了表名的複數形式。如下:

config := &gorm.Config{
 NamingStrategy: schema.NamingStrategy{
     SingularTable: true, // 禁用表名複數
 },
}

這個的作用就是我們在建立和表對應的 Model 結構體時,結構體的名稱會轉換成對應的表名,但使用的是單數形式。例如,我們有一個 MTest 的 Model 結構體。那麼,默認情況下,gorm 會把該 model 轉換成對應的 m_tests 表名。而如果做了對應的配置:SingularTable:True,gorm 就會把該 model 轉換成對應的 m_test 表名。

以下就是 Config 結構中的核心字段(我們省略了一些):

// Config GORM config
type Config struct {
 // NamingStrategy tables, columns naming strategy
 NamingStrategy schema.Namer
 // DryRun generate sql without execute
 DryRun bool
 // ConnPool db conn pool
 ConnPool ConnPool
 // Dialector database dialector
 Dialector
 // Plugins registered plugins
 Plugins map[string]Plugin

 callbacks  *callbacks
}

在該結構中,各字段如下:

三、Statement

statement 代表的是語句。這裏就包含了 sql 種涉及到的所有語句了。其結構如下:

type Statement struct {
 *DB
 TableExpr            *clause.Expr //表表達式
 Table                string //表名
 Model                interface{} //model結構體
 Dest                 interface{} // 接收查詢結果的變量
 ReflectValue         reflect.Value //model類型的value值
 Clauses              map[string]clause.Clause //sql語句
 BuildClauses         []string
 Distinct             bool //是否去重
 Selects              []string // 要查詢的字段列表
 Omits                []string // 忽略的字段 
 Joins                []join // join的表
 Preloads             map[string][]interface{}
 Settings             sync.Map
 ConnPool             ConnPool
 Schema               *schema.Schema // 對應的表的模式
 Context              context.Context
 SQL                  strings.Builder //最終編譯好的sql語句
 Vars                 []interface{} // 從句中涉及到的變量值
}

這裏只列出了一些關鍵的字段。我們分類講解一些這些關鍵字段。

表相關字段

TableExpr 和 Table:這兩個字段都是通過 DB.Table(name string, args ...interface{}) 函數指定的。比如我們常用的 DB.Table("m_test") 就可以指定要查詢的表名。

Model 和 Schema 字段

通過 model 字段可以指定和數據表對應的結構體類型。然後 gorm 再通過 model 結構體轉換成對應的表的建表模式,並將其複製到 Schema 字段中。

Dest 字段

Dest 字段用來接收從數據表中查詢的結果。我們看 Dest 的類型是 interface{},也就是說可以是任意類型。Dest 一般是通過 Find 函數、Save、Create 等函數傳進來的。如下各函數的原型:

// dest會複製給Dest字段
func (db *DB) Find(dest interface{}, conds ...interface{}) (tx *DB)

//  會將value參數賦值給Dest字段
func (db *DB) Save(value interface{}) (tx *DB)

//  會將value參數賦值給Dest字段
func (db *DB) Create(value interface{}) (tx *DB)

同時,這裏還會有一個特點,就是當 Model 字段爲 nil 時,會將 Dest 字段賦值給 Model 字段。這也就是爲什麼我們在使用 gorm 的時候,給 Find 函數傳一個 Model 類型的 dest 就能定位到對應的表,並將數據獲取出來。而給 Find 函數傳遞一個 map 類型的 dest,就必須要通過 Table 函數指定一個表名的原因。如下:

// 定義一個MTest的model
type MTest struct {
 Id   int64
 Name string `gorm:"DEFAULT:John"`
}

// 定義一個model變量
var row MTest

// 未指定表,依然能從m_test表中查找到數據
err := db.Find(&row)


// 示例二
// 定義一個MTest的model
type MTest struct {
 Id   int64
 Name string `gorm:"DEFAULT:John"`
}

// 定義一個map類型用來接收數據
var row = make(map[string]interface{},0)

// 需要指定model,才能從m_test表中查找到數據
err := db.Model(MTest{}).Find(&row)

四、Schema

Schema 字段是通過解析 Model 字段而得到的值,和數據表的模式對應。一個數據表的模式包含表名、字段及字段默認值、主鍵字段。以下是 Schema 結構體的一些重點字段:

type Schema struct {
 Name                      string //model的名稱
 ModelType                 reflect.Type
 Table                     string //表名
 PrioritizedPrimaryField   *Field //最優先的主鍵ID
 DBNames                   []string //通過在model中指定tag(COLUMN)來和數據表中的字段關聯
 PrimaryFields             []*Field //主鍵字段集合
 PrimaryFieldDBNames       []string //主鍵字段集合(數據庫中的字段名)
 Fields                    []*Field //結構體中的字段名、數據庫中的字段名
 FieldsByName              map[string]*Field //以結構體中的字段名爲key的map
 FieldsByBindName          map[string]*Field // embedded fields is 'Embed.Field' 嵌入的字段名稱
 FieldsByDBName            map[string]*Field // 以數據庫中的字段名爲key的map
 FieldsWithDefaultDBValue  []*Field // fields with default value assigned by database 有默認值的字段
 Relationships             Relationships //相關聯的表
}

在該結構體中,最核心的字段就是 Fields 字段。該 Fields 字段就是從對應的 Model 結構體中通過 reflect 解析出來的字段。該字段默認是跟數據表中的字段一一對應的。

五、Clause

在 sql 語句中,各個關鍵詞對應的就是從句,即 Clause。比如 Select、From、Where、Order、Group By、Limit 等等。在 gorm 中,會通過對應名稱的函數來組織對應的從句。比如 Where 從句:

// 組織成Where從句
func (db *DB) Where(query interface{}, args ...interface{}) (tx *DB)

// 組織成GroupBy從句
func (db *DB) Group(name string) (tx *DB)

比如以下就是指定了 Where 的從句:

type MTest struct {
 Id   int64
 Name string `gorm:"DEFAULT:John"`
}

var row MTest

err := db.Where("id=?", 1).Find(&row)

在 gorm 中,將各個從句定義成了對應的類型。同時,這些類型又實現了 Clause 接口類型。如下是 Clause 接口的定義:

// Interface clause interface
type Interface interface {
 Name() string
 Build(Builder)
 MergeClause(*Clause)
}

在這個接口中,有一個 Build 函數需要額外注意。各個具體的從句就是通過 Build 這個函數來轉換成對應的 sql 語句的。比如 Where 從句:

// Build build where clause
func (where Where) Build(builder Builder) {
 // Switch position if the first query expression is a single Or condition
 for idx, expr := range where.Exprs {
  if v, ok := expr.(OrConditions); !ok || len(v.Exprs) > 1 {
   if idx != 0 {
    where.Exprs[0], where.Exprs[idx] = where.Exprs[idx], where.Exprs[0]
   }
   break
  }
 }

 buildExprs(where.Exprs, builder, AndWithSpace)
}

總結

我們從 DB 結構開始,逐級的講解了 gorm 中核心的數據結構:DB、Statement、Schema、Clause 以及 Config。同時,通過示例指出各個結構值是如何被賦值的。通過了解核心數據結構,能夠幫助我們更好的使用 gorm。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/NeygSaQvTRpOl-DCsDnzpw