Gorm 使用 | 青训营笔记

121 阅读4分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 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 作为主键,使用结构体名的 蛇形复数 作为表名,字段名的 蛇形 作为列名,并使用 CreatedAtUpdatedAt 字段追踪创建、更新时间。

Gorm 提供了一个默认的 model,定义在 gorm.io/gorm 包内,具体定义如下。

type Model struct {
    ID        uint `gorm:"primarykey"`
    CreatedAt time.Time
    UpdatedAt time.Time
    DeletedAt DeletedAt `gorm:"index"`
}

其中,CreatedAtUpdatedAtDeletedAt 字段会在对象创建、更新和删除时默认进行填充,无需手动填写。所以可以极大减少工作量。

在定义对象时,可以通过在结构体例添加 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 错误进行额外的处理。

毕竟这并不是真实意义下的查询操作错误,而只是没有找到需要的结果,查询操作过程中并没有发生错误。

因此,在实际业务中针对查询结果的处理最好通过查询结果对象来判断。查询结果对象

  1. 若为 nil,则说明发生数据库查询执行错误
  2. 若不为 nil,而 len()0,说明没有查到数据
  3. 若不为 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)
})