Gorm基础操作 | 青训营笔记

93 阅读8分钟

这是我参与「 第五届青训营 」伴学笔记创作活动的第 4 天 Gorm是Golang的一个orm框架,orm是"对象-关系映射"(Object/Relational Mapping)的缩写,我们可以使用操作对象的方式操作数据库,使得数据库的操作变的更加方便。

重点内容

  • 数据库的连接与配置
  • 数据模型定义(映射)
  • CURD接口的使用

数据库的连接与配置

1. gorm及驱动下载

文章中使用的gorm为v2版本,数据库使用mysql

mysql驱动包: gorm.io/driver/mysql
gorm主体包: gorm.io/gorm

2. 数据库的连接

使用dsn格式进行连接,需要配置DSN属性

[username[:password]@][protocol[(address)]]/dbname[?param1=value1&...&paramN=valueN]

  • 参数charset指定数据格式
  • parseTime如果设置为true,mysql中的datedatetime类型将自动转换为go中的Time.time类型。若不配置或为false,将转换为[]bytestring类型
  • loc设置转换为time.Time 类型时,使用的时区信息,默认值UTC,表示解析为UTC时间。一般设置为Local,表示使用当地时间。
var DB *gorm.DB // 声明DB对象供系统使用

func InitDataBase() {
   username := "root"  // 用户名
   password := ""      // 密码
   host := "127.0.0.1" // 数据库host
   port := ":3306"     // 数据库端口
   database := "gorm"  // 需要连接的数据库名
   dsn := strings.Join([]string{username, ":", password, "@tcp(", host, port, ")/", database, "?charset=utf8mb4&parseTime=True&loc=Local"}, "")

   db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
   if err != nil {
      log.Fatalln(err)
   }
   DB = db
}

3. 数据库配置

  1. mysql驱动的配置
db, err := gorm.Open(mysql.New(mysql.Config{
   DSN:                       dsn,   // 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 版本自动配置
}), &gorm.Config{})
  1. gorm的配置
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
   // 输出sql信息
   Logger: logger.Default.LogMode(logger.Info),
   // 关闭默认事务
   SkipDefaultTransaction: true,
   // 换成预编译语句
   PrepareStmt: true,
   // 取消外键约束
   DisableForeignKeyConstraintWhenMigrating: true,
   // 命名策略配置
   NamingStrategy: schema.NamingStrategy{
      TablePrefix:   "t_", // 表名前缀
      SingularTable: true, // 默认不加复数
   }})
  1. 连接池配置
mysqlDB, err := db.DB()
// SetMaxIdleConns 设置空闲连接池中连接的最大数量
mysqlDB.SetMaxIdleConns(10)
// SetMaxOpenConns 设置打开数据库连接的最大数量。
mysqlDB.SetMaxOpenConns(100)
// SetConnMaxLifetime 设置了连接可复用的最大时间。
mysqlDB.SetConnMaxLifetime(time.Hour)
  1. 常规配置模板
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
   Logger: logger.Default.LogMode(logger.Info),     // 输出sql信息
   DisableForeignKeyConstraintWhenMigrating: true,  // 取消外键约束
   NamingStrategy: schema.NamingStrategy{
      SingularTable: true, // 默认不加复数
   }})
if err != nil {
   log.Fatalln(err)
}
mysqlDB, err := db.DB()
// SetMaxIdleConns 设置空闲连接池中连接的最大数量
mysqlDB.SetMaxIdleConns(10)
// SetMaxOpenConns 设置打开数据库连接的最大数量。
mysqlDB.SetMaxOpenConns(100)
// SetConnMaxLifetime 设置了连接可复用的最大时间。
mysqlDB.SetConnMaxLifetime(time.Hour)

数据模型的定义

1. 字段标签

根据使用频率筛选的比较常用的字段,原文请见:Gorm官方文档-模型定义

标签名说明
column指定 db 列名
type列数据类型
size指定列数据大小/长度, 如: size:256
primaryKey指定列作为主键
unique指定列作为unique
default指定列的默认值
precision指定列的精度
not null指定列不为空
autoIncrement指定列自增
index使用选项创建索引

2. 模型定义

GORM 倾向于约定,而不是配置。默认情况下,GORM 使用 ID 作为主键,使用结构体名的 蛇形复数 作为表名,字段名的 蛇形 作为列名,并使用 CreatedAtUpdatedAt 字段追踪创建、更新时间。蛇形命名(sanke case)要求短语内的各个单词或缩写之间以_(下划线)做间隔且首字母为小写,例如CreatedAt的蛇形为created_at

  • gorm.Model会在创建时自动写入CreatedAtUpdatedAt为当前时间,DeletedAtnull
  • gorm.Model会在更新时自动写入UpdatedAt为当前时间,DeletedAtnull
  • gorm.Model会在删除时自动写入DeletedAt为当前时间
// gorm.Model
type Model struct {
   ID        uint `gorm:"primarykey"`
   CreatedAt time.Time
   UpdatedAt time.Time
   DeletedAt DeletedAt `gorm:"index"`
}

type User struct {
   gorm.Model
   Name string
   Age  int
}

// 等效于
type User struct {
    ID        uint `gorm:"primaryKey"`
    CreatedAt time.Time
    UpdatedAt time.Time
    DeletedAt gorm.DeletedAt `gorm:"index"`
    Name      string
    Age       int
}

CURD接口的使用

创建

user := User{Name: "wxl", Age: 22}
users := []User{user, user}
userMap := map[string]interface{}{"name": "wxl", "age": 22}
usersMap := []map[string]interface{}{userMap, userMap}

// 基础创建
// INSERT INTO `user` (`created_at`,`updated_at`,`deleted_at`,`name`,`age`) VALUES ('2023-01-20 15:05:45.41','2023-01-20 15:05:45.41',NULL,'wxl',22)
DB.Create(&user)

// 选取指定的字段创建
// INSERT INTO `user` (`created_at`,`updated_at`,`name`) VALUES ('2023-01-20 16:19:07.793','2023-01-20 16:19:07.793','wxl')
DB.Select("Name").Create(&user)

// 批量插入
// INSERT INTO `user` (`created_at`,`updated_at`,`deleted_at`,`name`,`age`) 
// VALUES ('2023-01-20 16:22:48.087','2023-01-20 16:22:48.087',NULL,'wxl',22),
// ('2023-01-20 16:22:48.087','2023-01-20 16:22:48.087',NULL,'wxl',22)
DB.Create(users)

// 根据map创建
DB.Model(&User{}).Create(&userMap)

// 根据map批量创建
DB.Model(&User{}).Create(&usersMap)

注意事项 使用结构体插入如数值、字符、布尔值的的变量时,若结构体没有对其进行赋值,gorm将使用其对应的默认值(零值)如:0''false,若要避免使用默认零值时。
在不改变数据库结构的前提下选方式有

  • 根据业务逻辑修改数据模型指定default值(推荐)
  • 使用Map进行插入数据的操作(推荐)
  • 修改数据模型选择使用指针类型(不推荐)
type User struct {
   Name string
   Age  int
}

// INSERT INTO `user` (`name`,`age`) VALUES ('',0)
DB.Create(&user)

type User struct {
   Name *string
   Age  *int
}

// INSERT INTO `user` (`name`,`age`) VALUES (NULL,NULL)
DB.Create(&user)

// INSERT INTO `user` (`age`,`name`) VALUES (NULL,'wxl')
DB.Model(&User{}).Create(map[string]interface{}{"name": "wxl", "age": nil})

type User struct {
   ID   int    `gorm:"primaryKey;autoIncrement"`
   Name string `gorm:"default:'wxl'"`
   Age  int    `gorm:"default:22"`
}

// INSERT INTO `user` (`name`,`age`) VALUES ('wxl',22)
DB.Create(&user)

查询

1. First & Last

FirstLast 方法会根据主键查找到第一个、最后一个记录, 它仅在通过 struct 或提供 model 值进行查询时才起作用。 如果 model 类型没有定义主键,则按第一个字段排序

var user User

// 获取第一条数据(主键升序)
// SELECT * FROM `user` WHERE `user`.`deleted_at` IS NULL ORDER BY `user`.`id` LIMIT 1
DB.First(&user)

// 获取最后一条数据(主键降序)
// SELECT * FROM `user` WHERE `user`.`deleted_at` IS NULL AND `user`.`id` = 5 ORDER BY `user`.`id` DESC LIMIT 1
DB.Last(&user)

// 根据主键查找
// SELECT * FROM `user` WHERE `user`.`id` = 10 AND `user`.`deleted_at` IS NULL ORDER BY `user`.`id` LIMIT 1
DB.First(&user, 10)

result := DB.First(&user)
// 返回找到的记录数
fmt.Println(result.RowsAffected)
// 返回错误结果
fmt.Println(result.Error)

2. Find
// 获取全部记录 
// SELECT * FROM `user` WHERE `user`.`deleted_at` IS NULL
DB.Find(&user) 

// SELECT * FROM `user` WHERE `user`.`id` IN (1,2,3) AND `user`.`deleted_at` IS NULL
DB.Find(&user, []int{1,2,3})

3.条件查询
// 获取第一条匹配的记录
// SELECT * FROM `user` WHERE name = 'wxl' AND `user`.`deleted_at` IS NULL ORDER BY `user`.`id` LIMIT 1
DB.Where("name = ?", "wxl").First(&user)

// IN
// SELECT * FROM `user` WHERE name IN ('wxl1','wxl') AND `user`.`deleted_at` IS NULL
DB.Where("name IN ?", []string{"wxl1", "wxl"}).Find(&user)

// LIKE
// SELECT * FROM `user` WHERE name LIKE '%wx%' AND `user`.`deleted_at` IS NULL
DB.Where("name LIKE ?", "%wx%").Find(&user)

// AND
// SELECT * FROM `user` WHERE (name = 'wxl' AND age >= '20') AND `user`.`deleted_at` IS NULL
DB.Where("name = ? AND age >= ?", "wxl", "20").Find(&user)

// BETWEEN
// SELECT * FROM `user` WHERE (id BETWEEN 1 AND 5) AND `user`.`deleted_at` IS NULL
DB.Where("id BETWEEN ? AND ?", 1, 5).Find(&user)

// Struct
// SELECT * FROM `user` WHERE `user`.`name` = 'wxl' AND `user`.`age` = 22 ORDER BY `user`.`name` LIMIT 1
DB.Where(&User{Name: "wxl", Age: 22}).First(&user)

// Map
// SELECT * FROM `user` WHERE `age` = 22 AND `name` = 'wxl'
DB.Where(map[string]interface{}{"name": "wxl", "age": 22}).Find(&user)

// 切片
// SELECT * FROM `user` WHERE `user`.`name` IN (1,2,3)
DB.Where([]int64{1, 2, 3}).Find(&users)

// Or
// SELECT * FROM `user` WHERE id = 1 OR id = 2
DB.Where("id = 1").Or("id = 2").Find(&users)

// Not
// SELECT * FROM `user` WHERE NOT id = 1
DB.Not("id = 1").Find(&user)

注意事项 当使用结构作为条件查询时,GORM只会查询非零值字段。这意味着如果您的字段值为0''false时,该字段不会被用于构建查询条件。若查询条件使用到零值时,可以使用Map构建查询条件。

// SELECT * FROM `user` WHERE `user`.`name` = 'wxl' ORDER BY `user`.`name` LIMIT 1
DB.Where(&User{Name: "wxl", Age: 0}).First(&user)

// SELECT * FROM `user` WHERE `age` = 0 AND `name` = 'wxl'
DB.Where(map[string]interface{}{"name": "wxl", "age": 0}).Find(&user)

更新

1. Save更新全部字段
// 更新前现进行查找 避免出现零值问题
DB.First(&user)
user.Name = "wxl123"
user.Age = 12
// UPDATE `user` SET `name`='wxl123',`age`=12,`created_at`='2023-01-20 16:14:18',`updated_at`='2023-01-20 19:43:43.828',`deleted_at`=NULL WHERE `user`.`deleted_at` IS NULL AND `id` = 5
DB.Save(&user)
2. Update更新单列
// 根据条件和 model 的值进行更新
// UPDATE `user` SET `name`='wxl666',`updated_at`='2023-01-20 19:57:13.696' WHERE name = 'wxl123' AND `user`.`deleted_at` IS NULL AND `id` = 5
DB.Model(&user).Where("name = ?", "wxl123").Update("name", "wxl666")
2. Updates更新多列
// 根据struct更新属性,只会更新非零值的字段
// UPDATE `user` SET `name`='wxlqweqwe',`updated_at`='2023-01-20 21:06:03.175' WHERE `user`.`deleted_at` IS NULL AND `id` = 5
DB.Model(&user).Updates(User{Name: "wxlqweqwe", Age: 0})

// 根据map更新属性
// UPDATE `user` SET `age`=10,`name`='wxlasdasd',`updated_at`='2023-01-20 21:06:03.215' WHERE `user`.`deleted_at` IS NULL AND `id` = 5
DB.Model(&user).Updates(map[string]interface{}{"name": "wxlasdasd", "age": 10})

// Select 和 Struct (可以选中更新零值字段)
// UPDATE `user` SET `name`='wxl',`updated_at`='2023-01-20 21:07:58.283' WHERE `user`.`deleted_at` IS NULL AND `id` = 5
DB.Model(&user).Select("Name").Updates(User{Name: "wxl", Age: 10})

删除

如果数据模型包含了一个gorm.DeletedAt 字段(gorm.Model 已经包含了该字段),它将自动获得软删除的能力。
拥有软删除能力的模型调用 Delete 时,记录不会被从数据库中真正删除。但GORM会将DeletedAt置为当前时间,并且你不能再通过正常的查询方法找到该记录。

// 软删除
// UPDATE `user` SET `deleted_at`='2023-01-20 21:10:38.672' WHERE `user`.`id` = 5 AND `user`.`deleted_at` IS NULL
DB.Delete(&user)

// 物理删除
// DELETE FROM `user` WHERE `user`.`id` = 5
DB.Delete(&user)

// 根据主键删除
// DELETE FROM `user` WHERE `user`.`id` = 10
DB.Delete(&User{}, 10)

// 条件删除
// DELETE FROM `user` WHERE name = 'wxl'
DB.Where("name = ?", "wxl").Delete(&user)

gorm提供的基于time的deletedAt删除标志,若使用1/0标志为法可以使用gorm的soft_delete插件,添加数据模型的tag可以修改删除标志。

gorm.io/plugin/soft_delete

注意 gorm中删除是将标志位置1,数据有效为0,所以建议设计字段为isDel

type User struct {
   ID    int
   Name  string
   Age   int
   IsDel soft_delete.DeletedAt `gorm:"softDelete:flag"`
}

// UPDATE `user` SET `is_del`=1 WHERE `user`.`id` = 27 AND `user`.`is_del` = 0
DB.Delete(&user)

参考文章

GORM官方文档
GORM 极速入门
gin框架学习-Gorm入门指南
Gorm之gorm.Config结构体字段详解