• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

GORM入门 | 青训营

武飞扬头像
_砳砳_
帮助2

GORM 是一个 golang 的 ORM 框架,功能强大,已经迭代了10年 ,被广泛应用。本文旨在介绍一下基本的 GORM 用法,仅做入门之用,想了解更多高级知识可参看官方文档

安装

在工作区使用以下两个命令即可:

go get -u gorm.io/gorm
go get -u gorm.io/driver/sqlite

模型定义

模型是具有基本Go类型、指针及他们的别名或实现ScannerValuer接口的自定义类型的普通结构体。 GORM 倾向于约定,而不是配置。默认情况下,GORM 使用ID作为主键,使用结构体名的蛇形复数作为表名,字段名的蛇形作为列名,并使用 CreatedAtUpdatedAt 字段追踪创建、更新时间。遵循 GORM 已有的约定,可以减少使用时的配置和代码量。

GORM 定义一个 gorm.Model 结构体,其包括字段 IDCreatedAtUpdatedAtDeletedAt,可以在使用时将他们嵌入到自己的结构体中。

// gorm.Model 的定义
type Model struct {
  ID        uint           `gorm:"primaryKey"`
  CreatedAt time.Time
  UpdatedAt time.Time
  DeletedAt gorm.DeletedAt `gorm:"index"`
}

此外,为结构体实现func TableName() string方法可以实现自定义表名。

连接到数据库

GORM 官方支持的数据库类型有: MySQL, PostgreSQL, SQlite, SQL Server。下面是一个连接MySQL的例子。

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

func main() {
  dsn := "user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
  db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
}

此处的 DSN 指 Data Source Name,格式为

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

更多细节参考这里

GORM 也支持自定义驱动和根据现有的数据库连接来初始化,此处不再展开。

对于不支持的数据库,可以选择复用/自行开发驱动来进行连接。

创建

下面都已Product结构作为例子来说明。

type Product struct {
	ID    uint   `gorm:"primarykey"`
	Code  string `gorm:"column:code"`
	Price uint   `gorm:"default:30"`
}

其中后面的标签分别代表主键、列名、默认值,更多tag参见 gorm.cn/zh_CN/docs/…

创建一条数据,可直接使用create方法。

p := &Product{Code: "001", Price: 10}
res := db.Create(p)

创建多条语句可使用切片。

products := []*Product{{Code: "001"}, {Code: "002"}, {Code: "003"}}
res := db.Create(products)

GORM 也支持根据 map[string]interface{}[]map[string]interface{}{} 创建记录,但此时主键不会自动填充。

GORM 也为不同数据库提供了兼容的 Upsert 支持,下面是一个简单的例子:

DB.Clauses(clause.OnConflict{DoNothing: true}).Create(&p)

相关的更多细节在这里

此处还有一个细节是链式调用,GORM 可在具体的操作之前加许多条件,但是注意一定要加在操作之前才会生效。

查询

查询一条数据可使用First方法,会根据主键升序返回第一条主键。

p := &Product{}
res := db.First(p)
//SELECT * FROM `products` ORDER BY `products`.`id` LIMIT 1

查询多条数据可使用Find方法。

products := make([]*Product, 0)
db.Where("price < 30").Find(&products)
//SELECT * FROM `products` WHERE price < 30
db.Where("price IN ?", []uint{10, 0}).Find(&products)
//SELECT * FROM `products` WHERE price IN (10,0)
db.Where("price LIKE ?", "00%").Find(&products)
//SELECT * FROM `products` WHERE code LIKE '00%'
db.Where("code > ? AND price < ?", "000", 30).Find(&products)
//SELECT * FROM `products` WHERE code > '000' AND price < 30
db.Where(&Product{Code: "", Price: 30}).Find(&products)
//SELECT * FROM `products` WHERE `products`.`price` = 30
db.Where(map[string]interface{}{"code": "003", "Price": 0}).Find(&products)
//SELECT * FROM `products` WHERE `Price` = 0 AND `code` = '003'

要注意First方法时,查询不到数据会返回 ErrRecordNotFound 错误,而使用Find方法时,查询不到数据则不会返回错误,需要手动检查。

当使用结构体作为条件进行查询时,不会查询零值(0,false等),而若想要使用零值做为查询条件,则需要使用Map来构建查询条件。

更新

更新单个列可使用 Update 方法,当使用 Update 更新单个列时,需要指定条件,否则会返回 ErrMissingWhereClause 错误

p := &Product{}
db.Model(p).Where("price = 0").Update("code", "111")
//UPDATE `products` SET `code`='111' WHERE price = 0

此处的Model方法是为了选定表,前面不需要是因为前面可以通过参数确认,此处需要指明。另外,当使用了 Model 方法,且该对象主键有值,该值会被用于构建条件。

更新多个列需要使用 Updates 方法。

db.Model(p).Where("price = 10").Updates(Product{Price: 0, Code: "222"})
//UPDATE `products` SET `code`='222' WHERE price = 0
db.Model(p).Where("price = 10").Updates(map[string]interface{}{"price": 0, "code": "333"})
//UPDATE `products` SET `code`='333',`price`=0 WHERE price = 10

同样的,使用结构体不会更新零值,更新零值需要使用 Map 或 Select 选择字段。

使用 Select方法可以更新选定字段。

db.Model(p).Where("price = 10").Select("code").Updates(map[string]interface{}{"price": 0, "code": "333"})
//UPDATE `products` SET `code`='333',`price`=0 WHERE price = 10

GORM 也允许使用 SQL 表达式进行更新。

db.Model(p).Where("price = 10").Update("price", gorm.Expr("price * ?", 3))
//UPDATE `products` SET `price`=price * 3 WHERE price = 10

删除

物理删除可使用 Delete 方法。

GORM 允许使用内联条件指定删除对象的主键,但只支持整形主键。

p := &Product{}
db.Delete(p, 10)
// DELETE FROM 'products' WHERE id = 10;
db.Delete(p, "10")
// DELETE FROM 'products' WHERE id = 10;
db.Delete(p, []int{1,2,3})
// DELETE FROM 'products' WHERE id IN (1,2,3);
db.Model(p).Where("price = 0").Delete(p)
//DELETE FROM `products` WHERE price = 0
db.Model(p).Delete(p, "price = ?", 0)
//DELETE FROM `products` WHERE price = 0

如果模型中包含了一个 gorm.deletedat 字段(gorm.Model 已经包含了该字段),它将拥有软删除的能力。

拥有软删除能力的模型调用 Delete 时,记录不会被从数据库中真正删除。但 GORM 会将 DeletedAt 置为当前时间, 并且你不能再通过正常的查询方法找到该记录。

通过使用 Unscoped 可以找到被软删除的数据。

db.Unscoped().Where("price = 20").Find(&p)
// SELECT * FROM products WHERE price = 20;

同样的,调用 Unscoped 可以实现永久删除。

事务

GORM 提供了 BeginCommitRollback 方法用于事务。

tx := db.Begin()
if err := tx.Create(&Product{Code: "111"}).Error; err != nil {
    tx.Rollback()
}
if err := tx.Create(&Product{Code: "222"}).Error; err != nil {
    tx.Rollback()
}
tx.Commit()

注意当 Begin 之后,使用 tx 而不是 db 来操作数据库。

为避免漏写 CommitRollback,GORM提供了 Tansaction 方法用于自动提交事务。

db.Transaction(func(tx *gorm.DB) error {
    if err := tx.Create(&Product{Code: "111"}).Error; err != nil {
        return err
    }
    if err := tx.Create(&Product{Code: "222"}).Error; err != nil {
        tx.Rollback()
        return err
    }
    return nil
})

为了确保数据一致性,GORM 会在事务里执行写入操作(创建、更新、删除)。如果没有这方面的要求,可以在初始化时禁用它,这将获得大约 30% 性能提升。

db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{
  SkipDefaultTransaction: true,
})

HOOK

GORM 提供了 CRUD 的 HOOK 能力。HOOK 是在创建、查询、更新、删除等操作之前、之后调用的函数。

如果已经为模型定义了指定的方法,它会在创建、更新、查询、删除时自动被调用。如果任何回调返回错误,GORM 将停止后续的操作并回滚事务。

HOOK 方法的函数签名为 func(*gorm.DB) error

  • 创建对象
    // 开始事务
    BeforeSave
    BeforeCreate
    // 关联前的 save
    // 插入记录至 db
    // 关联后的 save
    AfterCreate
    AfterSave
    // 提交或回滚事务
    
  • 更新对象
    // 开始事务
    BeforeSave
    BeforeUpdate
    // 关联前的 save
    // 更新 db
    // 关联后的 save
    AfterUpdate
    AfterSave
    // 提交或回滚事务
    
  • 删除对象
    // 开始事务
    BeforeDelete
    // 删除 db 中的数据
    AfterDelete
    // 提交或回滚事务
    
  • 查询对象
    // 从 db 中加载数据
    // Preloading (eager loading)
    AfterFind
    

结语

GORM 已经存在了10年 ,具有相当好的生态,有着丰富的扩展,使用适当的扩展可以大大提高效率,值得了解一下。

本文仅仅是个入门,很多细节都没有讲到,可以去查看官方文档,获取更多信息。

最后,作者水平有限,如有任何错误,还望见谅。

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhgbakje
系列文章
更多 icon
同类精品
更多 icon
继续加载