当前位置: 首页 > news >正文

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

http://www.dtcms.com/a/395412.html

相关文章:

  • C++11 移动语义与右值
  • FPGA学习笔记——图像处理之对比度调节(直方图均衡化)
  • 如何进行人脸识别
  • 计算机视觉笔试选择题:题组1
  • 第八篇:常量表达式:从const到constexpr的革命
  • RV1126 NO.30:RV1126多线程获取音频AI的PCM数据
  • 基于蚁群算法解决车辆路径问题(VRP)的MATLAB实现
  • C语言自学--C语⾔内存函数
  • 磁带记录仪:从磁带到数字的数据存储之旅
  • 【运维】Ubuntu上WebDAV挂载与自动同步完整指南
  • Ubuntu之旅-04 Docker
  • python(73) 引用.dll文件并调用函数
  • Chrome 学习小记5——demo:(动态壁纸基础)
  • 手写 Android Dex VMP 壳:自定义虚拟机 + 指令解释执行全流程
  • 【Netty】创建一个 SSL 处理器,实现客户端与服务器之间的安全通信
  • 13 Python数据结构与算法
  • 爱:宇宙的心跳
  • Python字节数据写入文本文件完全指南:从基础到高级实战
  • 零基础Windows10安装LLVM
  • selenium三种等待方式详解
  • Leetcode总结速记
  • 手写 Android Dex VMP 壳:指令流 AES 加密 + 动态加载全流程
  • 视频融合平台EasyCVR国标GB28181视频诊断功能详解与实践
  • ORACLE adg 备库也能单独提取AWR报告
  • Angular由一个bug说起之十九:Angular 实现可拓展 Dropdown 组件
  • Kafka核心架构与高效消息处理指南
  • flink1.18配置多个上游source和下游sink
  • 快速查看自己电脑的ip地址:一个命令见本机私网ip,一步查询本地网络公网ip,附内网ip让外网访问的通用方法
  • 插件化(Plugin)设计模式——Python 的动态导入和参数解析库 argparse 的高级用法
  • 【JavaSE】【网络原理】UDP和TCP原理