GORM库用法查漏补缺
1 模型定义
1、小知识:默认情况下gorm库会以什么样的规则将结构体映射到某一张表?
结构体名称转换为表名称通常使用蛇形命名法转换,此外gorm库还会自动将名称设置为复数。当然,对于那些单复数相同的名词gorm是区分不出来的。如:Good->goods,gorm转复数只会无脑加s。那有什么办法可以让gorm只使用蛇形命名法而不加复数吗?
如下代码,只要我们在创建数据库连接时进行配置,即可关闭gorm的复数表名功能:
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{NamingStrategy: schema.NamingStrategy{SingularTable: true, // 关闭复数表名},
})
2、小知识:gorm库中提供了一个结构体Model,其作用是什么?
Model结构体包含了四个比较常用的字段 ID、CreateAt、UpdateAt和DeleteAt。为了提供便利,gorm库定义了Model结构体,当我们需要使用这些字段时,直接将Model结构体嵌入表对应的结构体中即可。此外,当Model中的DeleteAt嵌入结构体后会自动开启软删除功能,当我们删除一条记录时将执行逻辑删除,即只设置DeleteAt属性的时间,而不执行物理删除。
type Model struct {ID uint `gorm:"primarykey"`CreatedAt time.TimeUpdatedAt time.TimeDeletedAt DeletedAt `gorm:"index"`
}
3、embed标签标识
功能:对于一些包含属性比较多的结构体,如果将属性平铺结构体会很长。此时若部分属性之间具有数据泥团的特征,则可以封装为当前结构体的内嵌结构体,并使用gorm的embed标签标识。使用embed后的结构体和平铺属性的结构体是等价的。此外,embed内嵌的结构体还能使用embeddedPrefix标签统一指定部分属性的前缀。如下列代码中Blog1和Blog2结构体是等价的。
type Author struct {Name string `gorm:"column:name"`Email string `gorm:"column:email"`
}
// 使用gorm embed功能
type Blog1 struct {ID int `gorm:"column:id"`Author Author `gorm:"embedded";embeddedPrefix:author_`Upvotes int32 `gorm:"column:upvotes"`
}
// 属性平铺
type Blog2 struct {ID int `gorm:"column:id"`Upvotes int32 `gorm:"column:upvotes"`Name string `gorm:"column:author_name"`Email string `gorm:"column:author_email"`
}
2 查询
1、First、Take、Last与Find的区别?
四者都是用于查询数据的,它们的区别在于
前三个在找不到数据时会返回ErrRecordNotFound错误,而Find不会。
First、Last会根据主键进行排序然后通过limit 1返回第一条记录,Find则是查询满足条件的所有记录,然后在代码层面映射第一条记录到结果对象中,相较前两者的性能较低。
var users []User
// First 查询第一条符合条件的记录,SQL 会自动加 LIMIT 1
db.Where("age > ?", 18).First(&user)
// SELECT * FROM users WHERE age > 18 ORDER BY id LIMIT 1;
// Find 查询所有符合条件的记录
db.Where("age > ?", 18).Find(&users)
// SELECT * FROM users WHERE age > 18;
Find方法使用场景:
当查询条件为主键、唯一键且等值查询时使用Find。可以避免ErrRecordNotFound错误的额外判断。
当查询满足某条件的多个对象时使用Find,可以一次性查找出满足条件的所有记录。
First、Last、Take使用场景:
当查询条件是范围查询且只需要查询一条记录时使用First、Last、Take。这种场景Find会查询所有满足条件的记录,而这三个方法会设置Limit 1。因此,能查询到的数据越多,First等方法性能相较Find就越好。
2、小知识:对于查询条件较为简单的SQL语句,可以直接使用Find内联查询代替Find + Where。
// 下列两条查询SQL是等价的
db.Find(&user, "status = ? and update_time < ?", 1, time.Now())
db.Where("status = ? and update_time < ?", 1, time.Now()).Find(&user)
3、小知识:在只需要查询表中某一列的场景中,可以使用Pluck来代替Select + Find。
var ages []int64
// 下列两条查询SQL是等价的
db.Model(&User{}).Pluck("age", &ages)
db.Model(&User{}).Select("age").Find(&ages)
3 更新
1、小坑!:Update函数在更新记录时只会更新非零字段,因此对于属性中包含bool类型的记录需要小心。一旦遇到这种情况,应该使用Select + Update语句进行特定字段更新或者使用map[string]interface{} kv对结构进行更新。这个坑点几乎所有刚用gorm库的人都会踩上一脚,值得注意。
// 根据 `struct` 更新属性,只会更新非零值的字段
db.Model(&user).Updates(User{ID: 111, Name: "hello", Age: 18, Active: false})
// UPDATE users SET name='hello', age=18 WHERE id = 111;db.Model(&user).Updates(map[string]interface{}{"id": 111, "name": "hello", "age": 18, "active": false})
// UPDATE users SET name='hello', age=18, active=false WHERE id=111;db.Model(&user).Select("name", "age", "active").Updates(User{ID: 111, Name: "hello", Age: 18, Active: false})
// UPDATE users SET name='hello', age=18, active=false WHERE id=111;
2、小知识:使用gorm时,全局删除和全局更新操作都是被禁止的。但是,gorm也留了后门给用户,在某些必要场景下若需要开启全局更新和全局删除则将配置中的AllowGlobalUpdate置为true即可。
mysqlDB := mysql.Open("root:xxxxxxxx@tcp(127.0.0.1:3306)/smallProgram?charset=utf8mb4&parseTime=True&loc=Local")
db, err := gorm.Open(mysqlDB, &gorm.Config{AllowGlobalUpdate: true,
})
4 其他功能
1、小知识:在某些情况下,没有可供随意蹂躏的测试环境进行测试。若直接调用gorm库生成的SQL语句又怕出错导致脏数据,此时如何检查gorm生成的SQL语句?gorm库提供了DryRun(试运行)配置,当该功能开启时gorm库的结尾方法只会打印SQL语句而不会真正的执行。这样开发者就可以在SQL执行前先核查SQL的正确性了,大大降低了出错的风险。特别是对于删除SQL,强烈建议开启软删除 + DryRun功能进行检查。
mysqlDB := mysql.Open("root:twk123456@tcp(127.0.0.1:3306)/smallProgram?charset=utf8mb4&parseTime=True&loc=Local")
db, err := gorm.Open(mysqlDB, &gorm.Config{DryRun: true,
})
参考链接:https://mp.weixin.qq.com/s/SqUxGVLS9Ih15RgvdfsEBA?mpshare=1&srcid=0911GVC7j4Ypk8XOkfOZLDK0&sharer_shareinfo=7b3939d95fff98877645a416646fa4a5&sharer_shareinfo_first=7b3939d95fff98877645a416646fa4a5&from=singlemessage&scene=1&subscene=317&sessionid=1758213354&clicktime=1758252061&enterid=1758252061&ascene=1&fasttmpl_type=0&fasttmpl_fullversion=7914993-zh_CN-zip&fasttmpl_flag=0&realreporttime=1758252061659&devicetype=android-34&version=28003e5a&nettype=WIFI&abtest_cookie=AAACAA==&lang=zh_CN&exportkey=n_ChQIAhIQ34Qy8QNrLXuSe6cBDbtSWxL1AQIE97dBBAEAAAAAAC/KLV1k5jAAAAAOpnltbLcz9gKNyK89dVj054GmAanAnCgXw8QnPwzJ+KPYFkcdctbnseHIKDNlqqBIuo2zGR5brtP+qL8Uh/A8/RMaNcWwodUcTBF9cRgsGutEO2Nmm4Ai95oN/ORnmYeUpi+pSkVsgs5AcTjsoBLvQco8VxV9vSTTdbNki5zEPxD6zSQRjxqgdbF+kOOAm6gaZu74chfKMzfjN6+9aHgQWTADHt5VkUkQugYtT+Nw7Pjag0eLBE9SgjFLqrNNS275Xgkxqe20Qm3UWz26qaIPJUVqsa9qKRmq+yG2kzfH&pass_ticket=qe2evF0OL7Halr+DacrEuDh1ufpnWSXYlcWuXNgiC8rMyDxL2npFQGKVz6q72BVc&wx_header=3&color_scheme=light