这是我参与「 第五届青训营 」伴学笔记创作活动的第 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&...¶mN=valueN]
- 参数
charset指定数据格式 parseTime如果设置为true,mysql中的date、datetime类型将自动转换为go中的Time.time类型。若不配置或为false,将转换为[]byte或string类型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. 数据库配置
- 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{})
- 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, // 默认不加复数
}})
- 连接池配置
mysqlDB, err := db.DB()
// SetMaxIdleConns 设置空闲连接池中连接的最大数量
mysqlDB.SetMaxIdleConns(10)
// SetMaxOpenConns 设置打开数据库连接的最大数量。
mysqlDB.SetMaxOpenConns(100)
// SetConnMaxLifetime 设置了连接可复用的最大时间。
mysqlDB.SetConnMaxLifetime(time.Hour)
- 常规配置模板
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 作为主键,使用结构体名的 蛇形复数 作为表名,字段名的 蛇形 作为列名,并使用 CreatedAt、UpdatedAt 字段追踪创建、更新时间。蛇形命名(sanke case)要求短语内的各个单词或缩写之间以_(下划线)做间隔且首字母为小写,例如CreatedAt的蛇形为created_at。
- gorm.Model会在创建时自动写入
CreatedAt、UpdatedAt为当前时间,DeletedAt为null - gorm.Model会在更新时自动写入
UpdatedAt为当前时间,DeletedAt为null - 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
First、Last 方法会根据主键查找到第一个、最后一个记录, 它仅在通过 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)