gorm学习系列三:真正掌握Find函数的用法

大家好,我是渔夫子。

欢迎关注 Go学堂 送《go常见错误100例案例解析》pdf文档

今天我们来深入理解下在gorm中,Find函数的底层逻辑。

Find函数是用来从数据库中查询的。我们通常的用法是给Find函数指定一个定义的Model对象,然后再指定对应的查询条件,这样就能查询数据了。

我们通过一个示例来查看下。 首先,我们有一个数据表,并导入两条数据。如下:

go 复制代码
CREATE TABLE `m_tests` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `name` varchar(100) NOT NULL DEFAULT '',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4;

INSERT INTO test01.m_test (id,name) VALUES (1,'John'), (2,'Jack');

然后定义一个和表对应的Model结构体:

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

打开一个数据库连接:

go 复制代码
  dsn := "user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
  db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})

通过gorm的Find函数进行查询:

go 复制代码
var row MTest
db.Find(&row)

这样,我们就能将id=1的数据从数据库中查出并赋值到row中。这也是我们在研发中常用的做法。

但Find函数并不仅仅是如此。

深入剖析Find函数

我们先来看Find函数的原型,如下:

go 复制代码
func (db *DB) Find(dest interface{}, conds ...interface{}) (tx *DB)

第一个参数名是dest,通过参数名可知,dest是用来接收从数据库中查询出来的数据的。从数据类型来看,是interface{},即可以是任意的数据类型。

用map来接收查询结果

在使用map接收查询结果时,就必须要明确的指定要查询的表了。如下:

go 复制代码
var row map[string]interface{}
err := db.Find(&row).Error
fmt.Printf("debug-err:%+v\n", err)

大家看,直接给Find函数一个map类型的变量时,程序是会报错的:

go 复制代码
unsupported data type: &map[]: Table not set, please set it like: db.Model(&user) or db.Table("users")

是说没有指定具体要查询哪张表。

所以,当接收查询结果的变量是map类型时,则需要明确的指定是哪张表。如下:

go 复制代码
var row map[string]interface{}
// 通过model指定表
err := db.Model(MTest{}).Find(&row).Error
// 通过Table函数指定具体表名
err := db.Table("m_tests").Find(&row).Error
fmt.Printf("debug-err:%+v\n", err)

这样,我们就能把id=1的数据查询并存储到row变量中。

那这又是为什么Find的第一个参数是对应Model类型的变量时就可以不指定表名,而是map类型时就需要明确指定表名呢?

原因在于,gorm在执行具体的查询的时候,是从指定的Model对象中来解析表的。下面是gorm中db相关的数据结构。如下: 大家看,在Statement结构中,实际上有两个字段,一个是Dest,即Find查询函数中的第一个参数,用来接收查询结果用的。第二个字段是Model,这个是和具体的数据表对应的Model结构。

在具体执行查询语句时,程序会判断Model字段是否为nil,如果是nil,则将Dest赋值给Model 。然后再解析Model以便解析出对应的表。 当然,在解析Model时会判断该Model的类型是否是结构体类型,以及该结构体是否有对应的表。否则,就会跑出类型不支持,没有指定具体表的错误。

用数组来接收查询结果

上面的示例中,结果都是只有一条数据。当然,也可以给Find函数指定一个数组来接收多个查询结果。如下:

go 复制代码
var rows []MTest
err := db.Find(&rows).Error

通过指定数组,就能把表里的数据全都查询出来并用rows来接收。

在一开始的时候,我们是通过指定了一个非切片类型的变量来接收查询结果的,但只接收到了一行数据。我们通过将两者的sql语句打印出来看看有什么区别。

go 复制代码
	var row MTest

	row_sql := db.ToSQL(func(tx *gorm.DB) *gorm.DB {
		return tx.Find(&row)
	})

	fmt.Printf("接收的sql语句:%s\n", row_sql) //SELECT * FROM `m_tests`

	var rows []MTest
	rows_sql := db.ToSQL(func(tx *gorm.DB) *gorm.DB {
		return tx.Find(&rows)
	})

	fmt.Printf("切片接收的sql语句:%s\n", rows_sql) //SELECT * FROM `m_tests`

有没有发现两个sql语句是一样的。

这里要注意:Find函数如果在没有指定Where条件的情况下,都是全表扫描。无论是用切片接受数据还是用非切片接收一条数据。

总结

通过本文,我们了解到Find函数的第一个参数是接收查询结果的参数,而并不是通过该参数指定的数据表。 当没有显式的指定Model时,gorm的查询会自动的将Dest参数值赋值给Model。然后,查询函数会从Model解析表名。如果从Model中解析不到对应的表名,就会报错。最后,我们分析了Find函数查询一行和多行数据的区别。其本质上是扫描符合条件的所有数据,最后根据是否是切片类型来返回数据而已。

相关推荐
嚣张农民2 小时前
推荐3个实用的760°全景框架
前端·vue.js·程序员
梓羽玩Python2 小时前
推荐一款用了5年的全能下载神器:Motrix!全平台支持,不限速下载网盘文件就靠它!
程序员·开源·github
梓羽玩Python3 小时前
这款一站式AI体验平台值得收藏起来!GPT-4o、GPT-4o Mini、Claude 3.5 Sonnet免费使用!
人工智能·程序员·设计
架构师那点事儿3 小时前
golang 用unsafe 无所畏惧,但使用不得到会panic
架构·go·掘金技术征文
前端宝哥17 小时前
10 个超赞的开发者工具,助你轻松提升效率
前端·程序员
于顾而言20 小时前
【笔记】Go Coding In Go Way
后端·go
qq_1728055920 小时前
GIN 反向代理功能
后端·golang·go
XinZong20 小时前
【VSCode插件推荐】想准时下班,你需要codemoss的帮助,分享AI写代码的愉快体验,附详细安装教程
前端·程序员
follycat1 天前
2024强网杯Proxy
网络·学习·网络安全·go
OT.Ter1 天前
【力扣打卡系列】单调栈
算法·leetcode·职场和发展·go·单调栈