Gorm的基本使用 | 豆包MarsCode AI刷题

37 阅读8分钟

介绍

Gorm是一个迭代了十几年的ORM框架,对go开发者十分友好

Gorm是通过驱动的方式连接数据库,目前支持MySQL,SQLServer,PostgreSQL,SQLite和TiDB 如果需要连接其它类型的数据库,可以复用/自行开发驱动。

安装

在go mod的相同目录下打开终端,输入

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

这样就导入Gorm依赖以及Gorm支持MySQL的驱动

简单入门

模型

Gorm是通过将Go结构体映射成数据库表来简化数据库交互的。因此需要学习如何在Gorm中定义Model,才能充分利用Gorm的功能

模型定义

模型是使用普通结构体定义的。这些结构体可以包含具有基本Go类型,指针,或这些类型的别名。 自定义类型也是可以的,只需要实现database/sql包中的Scanner和Valuer接口

对于下面这个User模型

type User struct {  
  ID           uint           // Standard field for the primary key  
  Name         string         // A regular string field  
  Email        *string        // A pointer to a string, allowing for null values  
  Age          uint8          // An unsigned 8-bit integer 
  Birthday     *time.Time     // A pointer to time.Time, can be null  
  MemberNumber sql.NullString // Uses sql.NullString to handle nullable strings  
  ActivatedAt  sql.NullTime   // Uses sql.NullTime for nullable time fields  
  CreatedAt    time.Time      // Automatically managed by GORM for creation time  
  UpdatedAt    time.Time      // Automatically managed by GORM for update time  
  ignored      string         // fields that aren't exported are ignored  
}
  • 具体基本类型如uint,string,uint8直接使用
  • 指针类型*string,*time.Time表示可空字段
  • database/sql 包的 sql.NullString 和 sql.NullTime 用于具有更多控制的可空字段。
    • CreatedAt 和 UpdatedAt 是特殊字段,当记录被创建或更新时,GORM 会自动向内填充当前时间。
  • 小写字母开头的字段名,就是非导出字段,不会被映射

约定

  • Gorm默认使用名为ID的字段作为每个模型的主键
  • 默认情况下,Gorm使用结构体的蛇形复数形式作为表名
  • 自动使用字段名的蛇形作为列名
  • 使用CreatedAt``UpdatedAt字段自动跟踪记录创建和更新时间 遵循这些约定有利于提升开发效率。但是Gorm也支持自定义这些配置。这里不过多介绍

gorm.Model

这是Gorm自带的结构体,名为gorm.Model

type Model struct {  
  ID        uint           `gorm:"primaryKey"`  
  CreatedAt time.Time  
  UpdatedAt time.Time  
  DeletedAt gorm.DeletedAt `gorm:"index"`  
}
  • 可以直接在我们自定义的结构体中嵌入这个类型,会自动包含这些字段。有助于在不同的模型间保持一致性,遵循Gorm的约定
  • 其中的DeletedAt字段用于软删除(将记录标记为已删除,实际在数据库仍然存在,只是打了个标记,就是懒标记吧)

连接到数据库

这里介绍最常用的MySQL的连接方式

// 参考 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{})

创建记录,插入记录

创建记录

user := User{Name: "Jinzhu", Age: 18, Birthday: time.Now()}  
  
result := db.Create(&user) // 通过数据的指针来创建  
  
user.ID             // 返回插入数据的主键  
result.Error        // 返回 error
result.RowsAffected // 返回插入记录的条数

使用Create()方法插入记录,传递的是一个数据的指针,就插入一个。 如果要一次性创建多条记录,就要传递一个指针数组,切片进去

注意,你无法向Create传递结构体,所以你应该传递一个数据的指针

users := []*User{  
    {Name: "Jinzhu", Age: 18, Birthday: time.Now()},  
    {Name: "Jackson", Age: 19, Birthday: time.Now()},  
}  
  
result := db.Create(users)

用指定的字段创建记录

创建记录,并为指定字段赋值

db.Select("Name", "Age", "CreatedAt").Create(&user)

创建记录,并且忽略传递给Omit的字段值

db.Omit("Name", "Age", "CreatedAt").Create(&user)

批量插入

要高效地插入大量记录,就用上面的传递一个切片给Create方法。Gorm将生成一条SQL来插入所有数据并返回所有主键值,并触发Hook方法。 当这些记录可以被分割成多个批次,Gorm会开启一个事务来处理它们。

var users = []User{{Name: "jinzhu1"}, {Name: "jinzhu2"}, {Name: "jinzhu3"}}  
db.Create(&users)  
  
for _, user := range users {  
  user.ID // 1,2,3  
}

通过db.CreateInBatches方法来指定批量插入的批次大小

var users = []User{{Name: "jinzhu_1"}, ...., {Name: "jinzhu_10000"}}  
  
// batch size 100  
db.CreateInBatches(users, 100)

简单查询

检索单个对象

Gorm提供了几个方法用于从数据库中查找单个对象,当查询数据库时添加了LIMIT 1条件,且没找到记录,会返回ErrRecordNotFound错误

获取第一条记录(主键升序)

db.First(&user)  // SELECT * FROM users ORDER BY id LIMIT 1;  

获取一条记录,没有指定排序字段

db.Take(&user)  // SELECT * FROM users LIMIT 1;  

获取最后一条记录(主键降序)

db.Last(&user)  // SELECT * FROM users ORDER BY id DESC LIMIT 1;  
result := db.First(&user)  
result.RowsAffected // 返回找到的记录数  
result.Error        // returns error or nil  

检查 ErrRecordNotFound 错误

errors.Is(result.Error, gorm.ErrRecordNotFound)

根据主键检索

如果主键是数字类型,您可以使用内联条件 来检索对象。

db.First(&user, 10)  
// SELECT * FROM users WHERE id = 10;  
  
db.First(&user, "10")  
// SELECT * FROM users WHERE id = 10;  
  
db.Find(&users, []int{1,2,3})  
// SELECT * FROM users WHERE id IN (1,2,3);

检索全部对象

// Get all records  
result := db.Find(&users)  
// SELECT * FROM users;  
  
result.RowsAffected // returns found records count, equals `len(users)`  
result.Error        // returns error

更新

保持所有字段

Save 会保存所有的字段,即使字段是零值

db.First(&user)   
user.Name = "jinzhu 2"  
user.Age = 100  
db.Save(&user)|

保存 是一个组合函数。 如果保存值不包含主键,它将执行 Create,否则它将执行 Update (包含所有字段)。

更新单列

当使用 Update 更新单列时,需要有一些条件,否则将会引起ErrMissingWhereClause 错误 当使用 Model 方法,并且它有主键值时,主键将会被用于构建条件

// 根据条件更新  
db.Model(&User{}).Where("active = ?", true).Update("name", "hello")  
// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE active=true;  
  
// User 的 ID 是 `111`db.Model(&user).Update("name", "hello")  
// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111;  
  
// 根据条件和 model 的值进行更新  
db.Model(&user).Where("active = ?", true).Update("name", "hello")  
// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111 AND active=true;

更新多列

Updates 方法支持 struct 和 map[string]interface{} 参数。当使用 struct 更新时,默认情况下GORM 只会更新非零值的字段

// 根据 `struct` 更新属性,只会更新非零值的字段  
db.Model(&user).Updates(User{Name: "hello", Age: 18, Active: false})  
// UPDATE users SET name='hello', age=18, updated_at = '2013-11-17 21:34:10' WHERE id = 111;  
  
// 根据 `map` 更新属性  
db.Model(&user).Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false})  
// UPDATE users SET name='hello', age=18, active=false, updated_at='2013-11-17 21:34:10' WHERE id=111;

更新选定字段

如果要在更新时选择、忽略某些字段,可以使用 SelectOmit

当然可以,下面是按照您的要求将代码块拆分,并将注释转换为文字说明后的结果:

选择 Map 的字段

db.Model(&user).Select("name").Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false})

UserID111 时,只更新 name 字段,其他字段即使在 map 中存在也不会被更新。这将执行如下 SQL 语句:

UPDATE users SET name='hello' WHERE id=111;

忽略特定字段进行更新

db.Model(&user).Omit("name").Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false})

这个示例忽略了 name 字段,因此不会更新该字段,而只会更新 ageactive 字段。假设 updated_at 是一个自动更新的时间戳字段,那么执行的 SQL 可能如下所示:

UPDATE users SET age=18, active=false, updated_at='2013-11-17 21:34:10' WHERE id=111;

选择 Struct 的字段(包括零值字段)

db.Model(&user).Select("Name", "Age").Updates(User{Name: "new_name", Age: 0})

此示例指定了要更新的字段 NameAge,即使 Age 的值为零也会被更新。这将执行以下 SQL 语句:

UPDATE users SET name='new_name', age=0 WHERE id=111;

选择所有字段(包括零值字段)

db.Model(&user).Select("*").Updates(User{Name: "jinzhu", Role: "admin", Age: 0})

使用 * 表示选择所有字段进行更新,这意味着即使是零值字段也会被包含在内。根据提供的结构体实例,SQL 语句将会是:

UPDATE users SET name='jinzhu', role='admin', age=0 WHERE id=111;

选择除特定字段外的所有字段(包括零值字段)

db.Model(&user).Select("*").Omit("Role").Updates(User{Name: "jinzhu", Role: "admin", Age: 0})

这个例子中,我们选择了除了 Role 字段之外的所有字段进行更新。即使提供了 Role 字段的值,它也不会被更新。SQL 语句可能看起来像这样:

UPDATE users SET name='jinzhu', age=0 WHERE id=111;

删除

删除一条记录

删除一条记录时,删除对象需要指定主键,否则会触发 批量删除

// Email 的 ID 是 `10`
db.Delete(&email)  
// DELETE from emails where id = 10;  
  
// 带额外条件的删除  

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

根据主键删除

GORM 允许通过主键(可以是复合主键)和内联条件来删除对象

db.Delete(&User{}, 10)  
// DELETE FROM users WHERE id = 10;  
  
db.Delete(&User{}, "10")  
// DELETE FROM users WHERE id = 10;  
  
db.Delete(&users, []int{1,2,3})  
// DELETE FROM users WHERE id IN (1,2,3);