这是我参与「第五届青训营 」伴学笔记创作活动的第 1 天
Gorm 是什么
Gorm 是一个针对 Golang 语言的 ORM 库,提供了包括 MySQL、PostgreSQL、SQLite、SQL Server 数据库的支持。
安装
下载 Gorm 库
go get -u gorm.io/gorm
下载数据库驱动
go get -u gorm.io/driver/mysql
go get -u gorm.io/driver/sqlite
go get -u gorm.io/driver/postgres
go get -u gorm.io/driver/sqlserver
数据库连接
Gorm 提供了通过 dsn 连接数据库的方式。在连接数据库前需要下载对应数据库的驱动。
以 MySQL 数据库为例。
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
var (
DB *gorm.DB
err error
)
func Init() {
dsn := "gorm:gorm@tcp(127.0.0.1:3306)/gorm?charset=utf8mb4&parseTime=True&loc=Local"
DB, err = gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic(err)
}
}
上述代码中,通过 gorm.Open() 函数获取到 gorm.DB 类型的可访问对象,在需要对数据库进行操作时进行使用。
数据库基本操作
首先定义一个 Gorm 可以反序列化的 User 对象。
type User struct {
Name string `gorm:"column:name;not null" json:"name"`
Age int64 `gorm:"column:age;not null" json:"age"`
}
基本的增删改查操作通过获取 gorm.DB 对象来进行操作。
func operation(db *gorm.DB) {
db.Create(&User{Name: "Marry", Age: 18})
var user User
var usrs []User
db.Where("name = ?", "Hello").First(&user)
db.Select("name", "age").Find(&users)
db.Model(&user).Update("Age", 20)
db.Where("name = ?", "Marry").Delete(&user)
}
Gorm 使用注意事项
Gorm 默认模型
在创建对象时,Gorm 在默认情况下使用 ID 作为主键,使用结构体名的 蛇形复数 作为表名,字段名的 蛇形 作为列名,并使用 CreatedAt、UpdatedAt 字段追踪创建、更新时间。
Gorm 提供了一个默认的 model,定义在 gorm.io/gorm 包内,具体定义如下。
type Model struct {
ID uint `gorm:"primarykey"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt DeletedAt `gorm:"index"`
}
其中,CreatedAt、UpdatedAt 和 DeletedAt 字段会在对象创建、更新和删除时默认进行填充,无需手动填写。所以可以极大减少工作量。
在定义对象时,可以通过在结构体例添加 gorm.Model 来引入上述结构体的字段,从而减少需要手写的配置。
如改变之前定义的 User 对象。
type User struct {
gorm.Model
Name string `gorm:"column:name;not null" json:"name"`
Age int64 `gorm:"column:age;not null" json:"age"`
}
可以发现,DeletedAt 字段类型并非 Golang 基本的时间类型 time.Time,而是 gorm.DeletedAt 类型。其原因是该类型默认使用 软删除 的方法,即记录不会从数据库中被真正删除,但无法通过普通查询方法访问到(进行查询时会忽略软删除的对象)。
如果需要真实查找和删除包含 gorm.DeletedAt 类型字段的数据,需要使用 Unscoped 方法。
查找软删除的记录
DB.Unscoped().Where("age = 30").Find(&users)
真实删除软删除的记录
DB.Unscoped().Delete(&user)
查询中的 ErrRecordNotFound 错误
在查询时,很常见的结果是在数据库中并没有找到想要的数据,因此在查询无记录情况下返回 nil 还是 error 是一个比较有争议的话题。
Gorm 查询提供了 First Last Take方法,这些方法对于查询结果的处理使用后者处理方案,即在查询无记录情况下,查询方法会返回 ErrRecordNotFound 错误。
因此,在对查询结果的错误处理时,不能简单使用 err != nil 来判断,而需要针对 ErrRecordNotFound 错误进行额外的处理。
毕竟这并不是真实意义下的查询操作错误,而只是没有找到需要的结果,查询操作过程中并没有发生错误。
因此,在实际业务中针对查询结果的处理最好通过查询结果对象来判断。查询结果对象
- 若为
nil,则说明发生数据库查询执行错误 - 若不为
nil,而len()为0,说明没有查到数据 - 若不为
0,说明查询得到了数据
应用上述逻辑需要在错误处理返回 nil, error 时忽略 ErrRecordNotFound 错误。
if err != nil && err != gorm.ErrRecordNotFound {
}
Gorm 中的默认事务
Gorm 对于写操作(创建、更新、删除),为了确保数据的完整性,会将它们封装在一个事务里。但毫无疑问,这会降低执行性能。
Gorm 的配置文件提供了字段可以禁用默认事务,在单一写操作时可以禁用默认事务来提高性能。
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
SkipDefaultTransaction: true,
})
Gorm 的 Find 查询
Gorm 提供了针对所有结果的查询操作方法 Find。该方法接收一个结构体对象数组,并将结果保存在其中。
但需要注意的是,Find 方法并没有默认实现锁机制。因此在并行场景下,如果多个协程同时进行查询后操作,无论是否使用事务,都会得到错误结果。
可以通过在查找时对对象的更新操作上锁来实现。
mysql.Main().Transaction(func(tx *gorm.DB) error {
sub := Subject{}
res := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Find(&sub, "id = ?", id)
})