學習 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
}
在該結構中,各字段如下:
-
ConnPool: 是和數據庫建立的真實連接。
-
Dialector: 是連接器。這個是一個接口,以適配各種類型的數據庫。比如 MySQL、ClickHouse 等。比如,在最開始傳入 Open 函數的第一個參數 mysql.Open(dsn) 就是一個 Dialector,說明要連接的是 mysql 數據庫。後續所有操作都是針對 mysql 數據庫的。
-
callbacks: callbacks 是一個結構體,該結構體包含了一個 map 結構的 processors 的字段。該 processors 是實際的執行器。會有 4 個對應的 processor,分別爲:CREATE、QUERY、DELETE 和 UPDATE。用於分別執行對應的語句。
-
DryRun: 該參數是一個是否執行最終 sql 語句的一個開關。如果爲 false,則執行 sql 語句,否則只將對應的函數編譯成 sql 語句,但不實際執行。
三、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