学习gorm系列五:gorm中的核心数据结构

大家好,我是渔夫子。

欢迎关注 「Go学堂 」,学习更多gorm知识。

今天咱们一起来学习下gorm中的几个核心数据结构。通过了解gorm底层的数据结构,能够让我们了解gorm底层的实现,以便更好的使用gorm。

在gorm中主要有5个核心结构:DB、Config、Statment、Clause和Schema。接下来我们就详细的看下每种数据结构以及各结构之间的关系。

一、DB

在使用gorm的时候,我们首先会使用gorm.Open方法和数据库建立连接,同时并返回一个gorm.DB结构。如下:

go 复制代码
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类型的,如下:

go 复制代码
// 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函数中传入的,如上面我们对数据表命名的配置中禁用了表名的复数形式。如下:

go 复制代码
config := &gorm.Config{
	NamingStrategy: schema.NamingStrategy{
    	SingularTable: true, // 禁用表名复数
	},
}

这个的作用就是我们在建立和表对应的Model结构体时,结构体的名称会转换成对应的表名,但使用的是单数形式。例如,我们有一个MTest的Model结构体。那么,默认情况下,gorm会把该model转换成对应的m_tests表名。而如果做了对应的配置:SingularTable:True,gorm就会把该model转换成对应的m_test表名。

以下就是Config结构中的核心字段(我们省略了一些):

go 复制代码
// 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种涉及到的所有语句了。其结构如下:

go 复制代码
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等函数传进来的。如下各函数的原型:

go 复制代码
// 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函数指定一个表名的原因。如下:

go 复制代码
// 定义一个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结构体的一些重点字段:

go 复制代码
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从句:

go 复制代码
// 组织成Where从句
func (db *DB) Where(query interface{}, args ...interface{}) (tx *DB)

// 组织成GroupBy从句
func (db *DB) Group(name string) (tx *DB)

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

go 复制代码
type MTest struct {
	Id   int64
	Name string `gorm:"DEFAULT:John"`
}

var row MTest

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

在gorm中,将各个从句定义成了对应的类型。同时,这些类型又实现了Clause接口类型。如下是Clause接口的定义:

go 复制代码
// Interface clause interface
type Interface interface {
	Name() string
	Build(Builder)
	MergeClause(*Clause)
}

在这个接口中,有一个Build函数需要额外注意。各个具体的从句就是通过Build这个函数来转换成对应的sql语句的。比如Where从句:

go 复制代码
// 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。

相关推荐
小咕聊编程5 分钟前
【含文档+源码】基于SpringBoot的过滤协同算法之网上服装商城设计与实现
java·spring boot·后端
追逐时光者6 小时前
推荐 12 款开源美观、简单易用的 WPF UI 控件库,让 WPF 应用界面焕然一新!
后端·.net
Jagger_6 小时前
敏捷开发流程-精简版
前端·后端
苏打水com7 小时前
数据库进阶实战:从性能优化到分布式架构的核心突破
数据库·后端
间彧8 小时前
Spring Cloud Gateway与Kong或Nginx等API网关相比有哪些优劣势?
后端
间彧8 小时前
如何基于Spring Cloud Gateway实现灰度发布的具体配置示例?
后端
间彧8 小时前
在实际项目中如何设计一个高可用的Spring Cloud Gateway集群?
后端
间彧8 小时前
如何为Spring Cloud Gateway配置具体的负载均衡策略?
后端
间彧8 小时前
Spring Cloud Gateway详解与应用实战
后端
EnCi Zheng10 小时前
SpringBoot 配置文件完全指南-从入门到精通
java·spring boot·后端