gorm笔记 | 青训营笔记

200 阅读8分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 15 天

简介

gorm是面向golang语言的一种ORM(持久层)框架,支持多种数据库的接入,例如MySQL,PostgreSQL,SQLite,SQL Server,Clickhouse。此框架的特点,弱化了开发者对于sql语言的掌握程度,使用提供的API进行底层数据库的访问。

使用步骤

一般而言对于数据库的使用步骤如下:

  • 创建连接。这个步骤一般会有引入一个数据库驱动的概念,我们的代码就是通过这个驱动去操作底层的数据库。
  • 利用连接执行sql语句,操作数据库。
  • 获取并解析结果。
  • 关闭连接。

连接数据库

其中 dsn 中的 user,pass,dbname 分别替换成你自己的数据库连接账号,密码,以及默认连接的哪个数据库。ip,port 则替换成数据库实例的 ip地址与端口号。

基本用法

import (
  "gorm.io/driver/mysql"
  "gorm.io/gorm"
)

func main() {
  // ex: root:rootpass@tcp(127.0.0.1:3306)/mydb?charset=utf8mb4&parseTime=True&loc=Local&timeout=10s
  dsn := "user:pass@tcp(ip:port)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
  db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
}

进阶用法

  • 支持各种高级配置,以及自定义数据库驱动
import (
  "my_mysql_driver"
  "gorm.io/gorm"
)

func main() {
    db, err := gorm.Open(mysql.New(mysql.Config{
      DSN: "gorm:gorm@tcp(127.0.0.1:3306)/gorm?charset=utf8&parseTime=True&loc=Local", // DSN data source name
      DefaultStringSize: 256, // string 类型字段的默认长度
      DisableDatetimePrecision: true, // 禁用 datetime 精度,MySQL 5.6 之前的数据库不支持
      DontSupportRenameIndex: true, // 重命名索引时采用删除并新建的方式,MySQL 5.7 之前的数据库和 MariaDB 不支持重命名索引
      DontSupportRenameColumn: true, // 用 `change` 重命名列,MySQL 8 之前的数据库和 MariaDB 不支持重命名列
      SkipInitializeWithVersion: false, // 根据当前 MySQL 版本自动配置
      DriverName: "my_mysql_driver",
    }), &gorm.Config{})
}

连接池

数据库操作都是通过连接去执行的,频繁创建与销毁连接,是需要花费较大代价的,因此一般都采用连接池对连接进行复用。GORM 使用 database/sql 维护连接池

sqlDB, err := db.DB()

// SetMaxIdleConns 设置空闲连接池中连接的最大数量
sqlDB.SetMaxIdleConns(10)

// SetMaxOpenConns 设置打开数据库连接的最大数量。
sqlDB.SetMaxOpenConns(100)

// SetConnMaxLifetime 设置了连接可复用的最大时间。
sqlDB.SetConnMaxLifetime(time.Hour)

模型定义

  • 模型定义就是将数据库中的表结构映射为代码层面的model

例如数据库表 user

CREATE TABLE `sys_user_info` (
  `id` int NOT NULL AUTO_INCREMENT COMMENT '主键id',
  `user_id` varchar(32) COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户id',
  `user_name` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '用户名',
  `user_addr` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '住址',
  `user_age` int NOT NULL COMMENT '年龄',
  `user_sex` tinyint NOT NULL DEFAULT '0' COMMENT '性别0男1女',
  `sys_ctime` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `sys_utime` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `is_delete` tinyint NOT NULL DEFAULT '0' COMMENT '是否删除',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
  • 对应的model
type BaseModel struct {
   Id       int64     `gorm:"primary_key"`
   SysCtime time.Time `gorm:"autoCreateTime"` //在新增记录时可以自动填充当前时间
   SysUtime time.Time `gorm:"autoUpdateTime"` //在新增和更新记录时可以自动填充当前时间
   IsDelete int8
}

type SysUserInfo struct {
   BaseModel
   UserID   string
   UserName string
   UserAddr string
   UserAge  int16
   UserSex  int8
}

func (SysUserInfo) TableName() string {
   //实现TableName接口,以达到结构体和表对应,如果不实现该接口,并未设置全局表名禁用复数,gorm会自动扩展表名为sys_user_infos(结构体+s)
   return "sys_user_info"
}

SQL操作

新增记录

常规指针创建

type BaseModel struct {
   Id       int64     `gorm:"primary_key"`
   SysCtime time.Time `gorm:"autoCreateTime"` //在新增记录时可以自动填充当前时间
   SysUtime time.Time `gorm:"autoUpdateTime"` //在新增和更新记录时可以自动填充当前时间
   IsDelete int8
}

type SysUserInfo struct {
   BaseModel
   UserID   string
   UserName string
   UserAddr string
   UserAge  int16
   UserSex  int8
}

func (SysUserInfo) TableName() string {
   //实现TableName接口,以达到结构体和表对应,如果不实现该接口,并未设置全局表名禁用复数,gorm会自动扩展表名为sys_user_infos(结构体+s)
   return "sys_user_info"
}

指定某些字段插入

//指定插入某些字段插入
db.Select("Name", "Age", "CreatedAt").Create(&user)
//指定某些字段不插入
db.Omit("Name", "is_delete", "CreatedAt").Create(&user)
//针对,创建时间与更新时间,也可以使用模型定义tag来定义默认值

删除记录

  • 删除操作可以在 model 上绑定一些hook函数,做前置检查,例如不能删除 admin账号等
  • 另外可以在模型定义的时候指定软删除字段,这样在执行删除操作的时候会对应转换为 update 操作
// 根据主键删除 DELETE FROM `sys_user_info` WHERE `sys_user_info`.`id` = 4
config.Db.Delete(&model.SysUserInfo{}, 4)
// 根据其它条件删除 DELETE FROM `sys_user_info` WHERE `user_name` = '猪八戒'
config.Db.Where("user_name", c.Query("user_name")).Delete(&model.SysUserInfo{})

修改记录

  • 修改操作同样可以在model上绑定一些hook函数,例如 BeforeUpdate BeforeSave
  • UpdateColumn/UpdateColumns 可跳过hook 函数与自动更新时间,类似于原生的sql操作
  • 注意在指定修改的模型时,如果将主键字段已经赋值的话一定会加入到where语句中
  • Updates使用的参数是model的话则不会更新默认值,如果需要默认值需要改成map[string]interface{},其中key为数据库中更新的列名称

根据主键修改

user := &model.SysUserInfo{}
user.ID = 1
// UPDATE `sys_user_info` SET `sys_utime`='2021-08-08 16:46:15.752',`user_name`='小麻皮',`user_addr`='深圳' WHERE `id` = 1
config.Db.Model(&user).Updates(model.SysUserInfo{UserName: "小麻皮", UserAddr: "深圳"})

根据其它条件修改

// 根据主键更新多列
user := &model.SysUserInfo{}
user.ID = 1
// 根据其它条件更新 UPDATE `sys_user_info` SET `sys_utime`='2021-08-08 16:46:15.754',`user_name`='小皮球',`user_addr`='深圳' WHERE user_addr = 'shenzhen'
config.Db.Model(&model.SysUserInfo{}).Where("user_addr = ?", "shenzhen").Updates(model.SysUserInfo{UserName: "小皮球", UserAddr: "深圳"})
// Omit 指定忽略Updates中map的指定的那些key更新 UPDATE `sys_user_info` SET `user_age`=18,`sys_utime`='2021-08-08 16:46:15.755' WHERE `id` = 1
config.Db.Model(&user).Omit("user_name").Updates(map[string]interface{}{"user_name": "小小飞", "user_age": 18})
// Select 指定 Updates 中的map只有哪些key更新 UPDATE `sys_user_info` SET `sys_utime`='2021-08-08 16:46:15.756',`user_name`='new_name',`user_age`=47 WHERE `id` = 1
config.Db.Model(&user).Select("user_name", "user_age").Updates(model.SysUserInfo{UserName: "new_name", UserAge: 47})

查询记录

scan类似Find都是用于执行查询语句,然后把查询结果赋值给结构体变量,区别在于scan不会从传递进来的结构体变量提取表名。使用 Scan 方法的时候需要我们显示指定数据库的表名。另外回调函数注册的不一样,Find 函数支持更多的 Callback 注入。

普通查询

var userList []*model.SysUserInfo
//返回的是全部字段
config.Db.Where("user_name = ?", userName).Find(&userList)
// 原生 SQL
db.Raw("SELECT * FROM sys_user_info WHERE name = ?", userName).Scan(& userList)

返回指定字段

var userList []*model.SysUserInfo
config.Db.Select("user_name", "id").Where("user_name = ?", userName).Find(&userList)

排序

var userList []*model.SysUserInfo
// 查询后排序
config.Db.Where("user_name = ?", c.Query("user_name")).Order("user_age").Order("sys_ctime desc").Find(&userList)

分页

var userList []*model.SysUserInfo
// 分页查询 SELECT * FROM `sys_user_info` WHERE user_age > 10 LIMIT 3 OFFSET 2;
config.Db.Where("user_age > 10").Offset(2).Limit(3).Find(&userList)

group 与 Having

type Result struct {
   Sex   int8
   Count int
}
var result []*Result
// Model 一定要指定,否则会以默认规则 results 为表名查找,或者 struct 绑定方法 TableName
config.Db.Model(&model.SysUserInfo{}).Select("user_sex as Sex", "count(*) as count").Group("user_sex").Having("count(*) > 1").Find(&result)

distinct

// distinct SELECT DISTINCT `user_name` FROM `sys_user_info` WHERE user_sex = 1
config.Db.Where("user_sex = 1").Distinct("user_name").Find(&userList)

join

var userList []*model.SysUserInfo
// join SELECT a.user_name,b.user_age FROM sys_user_info a left join sys_user_info b on a.user_id = b.user_id WHERE `a`.`user_name` = '猪八戒'
config.Db.Table("sys_user_info a").Select("a.user_name", "b.user_age").Joins("left join sys_user_info b on a.user_id = b.user_id").Where("a.user_name", c.Query("user_name")).Scan(&userList)

调试sql

有时候只是想知道当前的数据库操作的最终执行sql是什么样的,却不想直接影响数据库的数据可以使用如下方式

// 需要开启 DryRun: true 配置选项 开启这个配置所有sql都不会执行
DB, err := gorm.Open(mysql.Open(dsn), &gorm.Config{DryRun: true})
statement := DB.Where("user_age", 25).Delete(&model.SysUserInfo{}).Statement

log.Printf("del statement dryRun 只打印sql,不真正执行:%+v\n", statement.SQL.String())

总结

总结了如上这些基本用法,日常开发已经足够,还有许多进阶用法可以参考官网。下面总结一些标签与规范: gorm标签

  • column 指定 db 列名
  • type 列数据类型,推荐使用兼容性好的通用类型,例如:所有数据库都支持 bool、int、uint、float、string、time、bytes 并且可以和其他标签一起使用,例如:not null、size, autoIncrement… 像 varbinary(8) 这样指定数据库数据类型也是支持的。在使用指定数据库数据类型时,它需要是完整的数据库数据类型,如:MEDIUMINT UNSIGNED not NULL AUTO_INCREMENT
  • size 指定列大小,例如:size:256
  • primaryKey 指定列为主键
  • unique 指定列为唯一
  • default 指定列的默认值
  • precision 指定列的精度
  • scale 指定列大小
  • not null 指定列为 NOT NULL
  • autoIncrement 指定列为自动增长
  • autoIncrementIncrement 自动步长,控制连续记录之间的间隔
  • embedded 嵌套字段
  • embeddedPrefix 嵌入字段的列名前缀
  • autoCreateTime 创建时追踪当前时间,对于 int 字段,它会追踪秒级时间戳,您可以使用 nano/milli 来追踪纳秒、毫秒时间戳,例如:autoCreateTime:nano
  • autoUpdateTime 创建/更新时追踪当前时间,对于 int 字段,它会追踪秒级时间戳,您可以使用 nano/milli 来追踪纳秒、毫秒时间戳,例如:autoUpdateTime:milli
  • index 根据参数创建索引,多个字段使用相同的名称则创建复合索引,查看 索引 获取详情
  • uniqueIndex 与 index 相同,但创建的是唯一索引
  • check 创建检查约束,例如 check:age > 13,查看 约束 获取详情
  • <- 设置字段写入的权限, <-:create 只创建、<-:update 只更新、<-:false 无写入权限、<- 创建和更新权限 (没权限时写入nil值)
  • -> 设置字段读的权限,->:false 无读权限
    • 忽略该字段,- 无读写权限
  • comment 迁移时为字段添加注释

tag的常用标签在MySQL建表时指定好就可以, 大部分都不会用到, 一般为了项目的可读性可以将tag补满。

一些规范

ID int // 默认的主键
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt gorm.DeletedAt

上面四个字段是gorm规范的字段, 其会自动对它们进行处理。