当前位置: 首页 > 工具软件 > GORM Gen > 使用案例 >

Gorm 的使用

沈良策
2023-12-01


GORM 的介绍与安装


简介

GORM 是 Go 语言中一款性能很好的 ORM 库,对开发人员也相对比较友好,能够显著提升开发效率。

GORM 有很多特性,常用的核心特性如下:

  • 功能全。使用 ORM 操作数据库的接口,GORM 都有,可以满足开发中对数据库调用的各类需求。

  • 支持钩子方法。这些钩子方法可以应用在 Create、Save、Update、Delete、Find 方法中。

  • 支持 Auto Migration 。

  • 支持关联查询。

  • 支持多种关系数据库,例如 MySQL、Postgres、SQLite、SQLServer 等。

GORM 库主要用来完成以下数据库操作:

  • 连接和关闭数据库。连接数据库时,可能需要设置一些参数,比如最大连接数、最大空闲连接数、最大连接时长等。

  • 插入表记录。可以插入一条记录,也可以批量插入记录。

  • 更新表记录。可以更新某一个字段,也可以更新多个字段。

  • 查看表记录。可以查看某一条记录,也可以查看符合条件的记录列表。

  • 删除表记录。可以删除某一个记录,也可以批量删除。删除还支持永久删除和软删除。


安装

  • GORM 库

Linux 系统中直接打开命令行终端,安装 GORM 输入如下命令:

go get -u github.com/jinzhu/gorm
  • 数据库驱动

SQLite 数据库为例,输入如下命令:

go get -u gorm.io/driver/sqlite

使用示例

SQLite 数据库为例,编写如下程序:

package main

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

type Product struct {
 		gorm.Model
  		Code  string
 	 	Price uint
}

func main() {
  		db, err := gorm.Open(sqlite.Open("/home/Tao/test.db"), &gorm.Config{})
  		if err != nil {
    		panic("failed to connect database")
  		}

  		// 迁移 schema
  		db.AutoMigrate(&Product{})

  		// Create
  		db.Create(&Product{Code: "D42", Price: 100})

 		// Read
  		var product Product
  		db.First(&product, 1) // 根据整型主键查找
  		db.First(&product, "code = ?", "D42") // 查找 code 字段值为 D42 的记录

  		// Update - 将 product 的 price 更新为 200
  		db.Model(&product).Update("Price", 200)
  		// Update - 更新多个字段
  		db.Model(&product).Updates(Product{Price: 200, Code: "F42"}) // 仅更新非零值字段
  		db.Model(&product).Updates(map[string]interface{}{"Price": 200, "Code": "F42"})

  		// Delete - 删除 product
  		db.Delete(&product, 1)
}

GORM 基础操作


模型定义

GORM 使用模型(Models)来映射一个数据库表,默认情况下,使用 ID 作为主键,使用结构体名的 snake_cases 作为表名,使用字段名的 snake_case 作为列名,并使用 CreatedAtUpdatedAtDeletedAt 字段追踪创建、更新和删除时间。

使用 GORM 的默认规则可以减少代码量,此外也可以直接指明字段名和表名,例如以下模型:

type Animal struct {
  	AnimalID int64        // 列名 `animal_id`
  	Birthday time.Time    // 列名 `birthday`
 	 Age      int64        // 列名 `age`
}

上述模型对应的表名为 animals ,列名分别为 animal_idbirthdayage ,可以通过以下方式来重命名表名和列名,并将 AnimalID 设置为表的主键:

type Animal struct {
    	AnimalID int64     `gorm:"column:animalID;primarykey"` // 将列名设为 `animalID`
    	Birthday time.Time `gorm:"column:birthday"`            // 将列名设为 `birthday`
   	 	Age      int64     `gorm:"column:age"`                 // 将列名设为 `age`
}

func (a *Animal) TableName() string {
    	return "animal"
}

上面的代码中,通过使用 primaryKey 标签指定主键,通过使用 column 标签指定列名,通过给 Models 添加 TableName 方法指定表名。

数据库表通常会包含 4 个字段,具体如下所示:

  • ID:自增字段,也作为主键。

  • CreatedAt:记录创建时间。

  • UpdatedAt:记录更新时间。

  • DeletedAt:记录删除时间(软删除时有用)。

GORM 预定义了包含这 4 个字段的 Models ,在定义自己的 Models 时,可以直接内嵌到结构体内,例如:

type Animal struct {
    	gorm.Model
    	AnimalID int64     `gorm:"column:animalID"` // 将列名设为 `animalID`
   	 	Birthday time.Time `gorm:"column:birthday"` // 将列名设为 `birthday`
    	Age      int64     `gorm:"column:age"`      // 将列名设为 `age`
}

Models 中的字段能支持很多 GORM 标签,但如果不使用 GORM 自动创建表和迁移表结构的功能,很多标签是用不到的。

连接数据库

在进行数据库的 CURD 操作之前,首先需要连接数据库。以连接 MySQL 数据库为例,可以通过以下程序代码的方式实现 :

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

func main() {
  		// 参考 https://github.com/go-sql-driver/mysql#dsn-data-source-name 获取详情
  		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{})
}

如果需要 GORM 正确地处理 time.Time 类型,在连接数据库时需要带上 parseTime 参数;如果要支持完整的 UTF-8 编码,可将charset=utf8 更改为 charset=utf8mb4

GORM 支持连接池,底层是用 database/sql 包来维护连接池的,连接池设置如下:

sqlDB, err := db.DB()
sqlDB.SetMaxIdleConns(100)              // 设置 MySQL 的最大空闲连接数(推荐100)
sqlDB.SetMaxOpenConns(100)             // 设置 MySQL 的最大连接数(推荐100)
sqlDB.SetConnMaxLifetime(time.Hour)    // 设置 MySQL 的空闲连接最大存活时间(推荐10s)

创建记录

创建数据记录可以通过 db.Create() 方法,例如如下的程序代码:

type User struct {
  		gorm.Model
		Name         string
  		Age          uint8
  		Birthday     *time.Time
}

user := User{Name: "Jinzhu", Age: 18, Birthday: time.Now()}
result := db.Create(&user) // 通过数据的指针来创建

db.Create() 方法会返回如下 3 个值:

  • user.ID :返回插入数据的主键,这个是直接赋值给 user 变量。

  • result.Error :返回 error 。

  • result.RowsAffected :返回插入记录的条数。

当需要插入的数据量比较大时,可以批量插入,例如如下的程序代码:

var users = []User{{Name: "jinzhu1"}, {Name: "jinzhu2"}, {Name: "jinzhu3"}}

DB.Create(&users)

for _, user := range users {
  		user.ID // 1,2,3
}

删除记录

可以通过 Delete 方法删除记录,例如如下的程序代码:

// DELETE from users where id = 10 AND name = "jinzhu";
db.Where("name = ?", "jinzhu").Delete(&user)

GORM 也支持根据主键进行删除,例如如下的程序代码:

// DELETE FROM users WHERE id = 10;
db.Delete(&User{}, 10)

使用 db.Where 的方式进行删除有两个优点。

(1)第一个优点是删除方式更通用。使用 db.Where 不仅可以根据主键删除,还能够随意组合条件进行删除。

(2)第二个优点是删除方式更显式,这意味着更易读。如果使用db.Delete(&User{}, 10),你还需要确认User的主键,如果记错了主键,还可能会引入Bug。

此外,GORM 也支持批量删除,例如如下的程序代码:

db.Where("name in (?)", []string{"jinzhu", "colin"}).Delete(&User{})

GORM 支持两种删除方法:软删除和永久删除。

  • 软删除

软删除是指执行 Delete 时,记录不会被从数据库中真正删除。GORM会将 DeletedAt 设置为当前时间,并且不能通过正常的方式查询到该记录。如果模型包含了一个 gorm.DeletedAt 字段,GORM 在执行删除操作时,会软删除该记录。

下面的删除方法就是一个软删除:

// UPDATE users SET deleted_at="2013-10-29 10:23" WHERE age = 20;
db.Where("age = ?", 20).Delete(&User{})

// SELECT * FROM users WHERE age = 20 AND deleted_at IS NULL;
db.Where("age = 20").Find(&user)

可以看到,GORM 并没有真正把记录从数据库删除掉,而是只更新了 deleted_at 字段。在查询时,GORM 查询条件中新增了 AND deleted_at IS NULL 条件,所以这些被设置过 deleted_at 字段的记录不会被查询到。

对于一些比较重要的数据,可以通过软删除的方式删除记录,软删除可以使这些重要的数据后期能够被恢复,并且便于以后的排障。

可以通过下面的方式查找被软删除的记录:

// SELECT * FROM users WHERE age = 20;
db.Unscoped().Where("age = 20").Find(&users)
  • 永久删除

如果想永久删除一条记录,可以使用 Unscoped ,例如如下的程序代码:

// DELETE FROM orders WHERE id=10;
db.Unscoped().Delete(&order) 

或者也可以在模型中去掉 gorm.DeletedAt

更新记录

GORM 中,最常用的更新方法,例如如下的程序代码:

db.First(&user)

user.Name = "jinzhu 2"
user.Age = 100
// UPDATE users SET name='jinzhu 2', age=100, birthday='2016-01-01', updated_at = '2013-11-17 21:34:10' WHERE id=111;
db.Save(&user)

上述方法会保留所有字段,所以执行 Save 时,需要先执行 First ,获取某个记录的所有列的值,然后再对需要更新的字段设置值。

还可以指定更新单个列,例如如下的程序代码:

// UPDATE users SET age=200, updated_at='2013-11-17 21:34:10' WHERE name='colin';
db.Model(&User{}).Where("name = ?", "colin").Update("age", 200)

也可以指定更新多个列,例如如下的程序代码:

// UPDATE users SET name='hello', age=18, updated_at = '2013-11-17 21:34:10' WHERE name = 'colin';
db.Model(&user).Where("name", "colin").Updates(User{Name: "hello", Age: 18, Active: false})

这里要注意,这个方法只会更新非零值的字段。

查询数据

GORM 支持不同的查询方法,下面我来讲解三种在开发中经常用到的查询方式,分别是检索单个记录、查询所有符合条件的记录和智能选择字段。

  • 检索单个记录

下面是检索单个记录,例如如下的程序代码:

// 获取第一条记录(主键升序)
// SELECT * FROM users ORDER BY id LIMIT 1;
db.First(&user)

// 获取最后一条记录(主键降序)
// SELECT * FROM users ORDER BY id DESC LIMIT 1;
db.Last(&user)
result := db.First(&user)
result.RowsAffected // 返回找到的记录数
result.Error        // returns error

// 检查 ErrRecordNotFound 错误 如果model类型没有定义主键,则按第一个字段排序。
errors.Is(result.Error, gorm.ErrRecordNotFound)
  • 查询所有符合条件的记录

例如如下的程序代码:

users := make([]*User, 0)

// SELECT * FROM users WHERE name <> 'jinzhu';
db.Where("name <> ?", "jinzhu").Find(&users)
  • 智能选择字段

可以通过 Select 方法,选择特定的字段,可以定义一个较小的结构体来接受选定的字段,例如如下的程序代码:

type APIUser struct {
  ID   uint
  Name string
}

// SELECT `id`, `name` FROM `users` LIMIT 10;
db.Model(&User{}).Limit(10).Find(&APIUser{})

高级查询

GORM 支持很多高级查询功能,这里主要介绍 4 种。

  • 指定检索记录时的排序方式

例如如下的程序代码:

// SELECT * FROM users ORDER BY age desc, name;
db.Order("age desc, name").Find(&users)
  • Limit & Offset

Offset 指定从第几条记录开始查询,Limit指定返回的最大记录数。Offset和Limit值为-1时,消除Offset和Limit条件。另外,Limit和Offset位置不同,效果也不同。

例如如下的程序代码:

// SELECT * FROM users OFFSET 5 LIMIT 10;
db.Limit(10).Offset(5).Find(&users)
  • Distinct

Distinct 可以从数据库记录中选择不同的值。

例如如下的程序代码:

db.Distinct("name", "age").Order("name, age desc").Find(&results)
  • Count

Count 可以获取匹配的条数。

例如如下的程序代码:

var count int64

// SELECT count(1) FROM users WHERE name = 'jinzhu'; (count)
db.Model(&User{}).Where("name = ?", "jinzhu").Count(&count)

GORM 还支持很多高级查询功能(如内联条件、Not 条件、Or 条件、Group & Having、Joins、Group、FirstOrInit、FirstOrCreate、迭代、FindInBatches 等)。

原生SQL

GORM 支持原生查询 SQL 和执行 SQL ,原生查询 SQL 用法如下:

type Result struct {
  		ID   int
  		Name string
  		Age  int
}

var result Result

db.Raw("SELECT id, name, age FROM users WHERE name = ?", 3).Scan(&result)

原生执行 SQL 用法,例如如下的程序代码:

db.Exec("DROP TABLE users")

db.Exec("UPDATE orders SET shipped_at=? WHERE id IN ?", time.Now(), []int64{1,2,3})

GORM 钩子

GORM 支持钩子功能,例如下面这个在插入记录前执行的钩子:

func (u *User) BeforeCreate(tx *gorm.DB) (err error) {
  u.UUID = uuid.New()

    if u.Name == "admin" {
        return errors.New("invalid name")
    }
    return
}

错误处理

GORM 中调用 db.Error()方法就能获取到错误信息,例如如下的程序代码:

var GormUser = new(GormUser)

err:= db.Model (6GormUser).Where("phone =?", "18988888888").
Update("phone","13888888888").Error
if err!=nil {
		// 错误处理
}

事务处理

GORM 中事务的处理也很简单,用 db.Begin() 方法声明开启事务,结束的时候调用 tx.Commit() 方法,异常的时候调用 tx.Rollback() 方法回滚,例如如下的程序代码:

//开启事务
tx:= db.Begin()

GormUser := GormUser(
Phone:"18988888888",
Name: "Shirdon",
Password: md5Password("666666"),//用户密码

if err := tx.Create(&GormUser).Error; err != nil {

		//事务回滚
		tx.Rollback()
		fmt.Println(err)
		db.First(6GormUser, "phone -?", "18988888888")

		//事务提交
		tx.Commit()
}

 类似资料: