【GO】GORM 利用教程

打印 上一主题 下一主题

主题 897|帖子 897|积分 2691

GORM 是一个用于 Go 语言的 ORM(Object-Relational Mapping) 库。它将关系型数据库的表与 Go 语言中的布局体相映射,允许开发者以面向对象的方式操作数据库,而不需要直接编写 SQL 语句。通过 GORM,开发者可以利用 Go 语言的布局体和方法来实行常见的数据库操作(如查询、插入、更新、删除等),大大简化了与数据库的交互过程。
1. 安装 GORM

起首,你需要安装 GORM 库。打开终端并运行以下命令:
  1. go get -u gorm.io/gorm
  2. go get -u gorm.io/driver/sqlite  # 示例数据库,可以根据需求更换为其他数据库驱动
复制代码
2. 创建底子布局体

ORM的一个核心概念是布局体,它代表数据库表的一个映射。例如,假设你有一个“用户”表,我们可以创建一个 User 布局体来表示它。
  1. package main
  2. import (
  3.     "gorm.io/driver/sqlite"
  4.     "gorm.io/gorm"
  5. )
  6. type User struct {
  7.     ID        uint   `gorm:"primaryKey"`  // 主键
  8.     Name      string `gorm:"size:100"`    // 用户名字段,限制长度为100
  9.     Age       uint   // 用户年龄
  10.     CreatedAt time.Time  // 创建时间(GORM 会自动管理)
  11. }
  12. func main() {
  13.     // 创建数据库连接
  14.     db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{})
  15.     if err != nil {
  16.         panic("failed to connect to the database")
  17.     }
  18.     // 自动迁移:通过GORM根据结构体自动创建表
  19.     db.AutoMigrate(&User{})
  20. }
复制代码
在 GORM 中,布局体字段的标签(tags)用于定义和控制怎样将 Go 布局体映射到数据库表的列。GORM 支持很多标签,可以设置数据库表的列属性、索引、关系等。以下是常见的 GORM 字段标签和它们的作用:
标签描述示例primaryKey指定字段为主键。gorm:"primaryKey"size指定字段的巨细,通常用于字符串字段。gorm:"size:100"unique创建唯一索引,确保字段值唯一。gorm:"unique"not null指定字段不能为空。gorm:"not null"default指定字段的默认值。gorm:"default:0"autoIncrement设置字段为自增字段。通常用于整数范例的主键。gorm:"autoIncrement"index为字段创建索引。gorm:"index"uniqueIndex为字段创建唯一索引。gorm:"uniqueIndex"index:<name>创建带有自定义名称的索引。gorm:"index:idx_name"foreignKey指定外键字段,在一对多或多对多关系中利用。gorm:"foreignKey:UserID"references指定外键关系中引用的字段。gorm:"references:ID"embedded嵌入一个布局体,扁平化嵌入的布局体字段到父布局体中。gorm:"embedded"preload在查询时预加载关联数据。gorm:"preload"createdAt主动生成的创建时间戳字段。gorm:"autoCreateTime"updatedAt主动生成的更新时间戳字段。gorm:"autoUpdateTime"deletedAt主动生成的删除时间戳字段,支持软删除。gorm:"softDelete"softDelete用于支持软删除功能。通常与 DeletedAt 配合利用。gorm:"index"column指定数据库中的列名,当字段名与列名不一致时利用。gorm:"column:db_column_name"type指定字段在数据库中的范例,通常用于特别范例(如 JSON)。gorm:"type:text"many2many用于多对多关系,指定毗连表的名称。gorm:"many2many:user_posts" 这个表格展示了 GORM 中常用的字段标签以及它们怎样影响数据库表布局,资助开发者更好地明白和利用 GORM 举行数据库操作。
3. 数据库毗连与设置

在上面的示例中,我们利用了 gorm.Open() 来毗连 SQLite 数据库。假如你利用 MySQL 或 Postgres 等其他数据库,可以更换相应的驱动。
例如,毗连到 MySQL 数据库的代码如下:
  1. db, err := gorm.Open(mysql.Open("user:password@tcp(localhost:3306)/dbname"), &gorm.Config{})
复制代码
4. 数据库操作

4.1 创建记载

你可以利用 Create 方法来插入数据。下面是怎样插入一个新用户:
  1. func createUser(db *gorm.DB) {
  2.     user := User{Name: "John Doe", Age: 30}
  3.     result := db.Create(&user)
  4.     if result.Error != nil {
  5.         panic("Failed to create user")
  6.     }
  7.     fmt.Println("User created:", user.ID)
  8. }
复制代码
代码中db.Create(&user) 利用 & 符号是由于我们要传递布局体 user 的 指针 给 Create 方法。具体来说,这样做有以下几个原因:


  • 指针传递可以修改原布局体
    GORM 的 Create 方法担当布局体的 指针,这样它可以直接修改原始布局体的值,而不但仅是副本。通过传递指针,GORM 可以或许在插入数据库的过程中修改布局体(例如,给布局体的字段赋值,例如数据库主动生成的 ID 或 CreatedAt 字段),确保布局体反映数据库中的最新数据。
    例如,user 的 ID 字段会在插入数据库时由 GORM 主动赋值(通常是自增的主键),假如你传递的是布局体的指针,Create 方法可以直接更新 user 布局体中的 ID 字段。
  • 制止复制大布局体
    假如你传递的是布局体的副本,GORM 会先创建一个布局体的拷贝并将其插入数据库。这对于较大的布局体来说可能会浪费内存并降低性能。而传递指针制止了复制整个布局体,只是传递了布局体的内存地址,性能更高。
  • GORM 的工作方式
    GORM 内部利用了指针来标识布局体字段的变革。通过传递指针,GORM 可以确定布局体的变革并举行相应的处理。例如,在实行 Create 时,GORM 会查抄布局体的指针,判断该字段是否已经赋值、是否需要主动添补等。
4.2 查询记载

GORM 提供了多种查询方式,可以通过布局体查询、条件查询等方式来获取数据。
获取单条记载

  1. func getUser(db *gorm.DB) {
  2.     var user User
  3.     result := db.First(&user, 1)  // 查找 user 表中主键为 1 的记录,并将其填充到 user 结构体中
  4.     if result.Error != nil {
  5.         panic("User not found")
  6.     }
  7.     fmt.Println("User:", user)
  8. }
复制代码
db.First 是 GORM 提供的一个查询方法,用于从数据库中获取 第一条 满意条件的记载。它通常用于根据主键或其他条件查询数据。
db.First 的基本语法:
  1. db.First(&model, conditions...)
复制代码


  • &model 是一个指针参数,表示查询的效果将会添补到这个布局体中。
  • conditions... 是查询的条件,可以是主键或其他字段。
假如查询乐成,db.First 会把查询到的记载添补到 model 指针所指向的布局体里。假如没有找到记载,它会返回一个错误。
在 db.First(&user, 1) 中,&user 是指向 user 布局体的指针。这里传递指针是由于 GORM 要修改 user 布局体的值(即添补查询效果)。


  • 通过传递布局体的指针,GORM 可以将查询效果直接赋值到 user 布局体中。
  • 假如你传递的是布局体自己,而不是指针,查询效果将不会添补到布局体中,由于布局体会作为副本传递到 db.First 方法,而 GORM 需要可以或许修改原始布局体的字段值。
获取多个记载

  1. func getUsers(db *gorm.DB) {
  2.     var users []User
  3.     result := db.Find(&users)
  4.     if result.Error != nil {
  5.         panic("No users found")
  6.     }
  7.     fmt.Println("Users:", users)
  8. }
复制代码
db.Find 是 GORM 提供的查询方法之一,用于查找多个记载并将其存储到传入的切片布局体中。


  • Find 方法会根据传入的条件来查找记载,可以是简单的查询(如所有记载),也可以是有条件的查询(如按字段值过滤)。
  • 传递给 Find 的参数是一个指针,它会将查询到的记载添补到指向的切片中。
db.Find(&users) 会从数据库中查找所有记载(或者根据传入的查询条件查找记载)并将它们添补到 users 切片中。查询的效果会是一个布局体的集合。Find 方法默认返回所有满意条件的记载。


  • 假如查询没有条件,Find 将返回数据库表中的所有记载。
  • 假如你传递了查询条件,Find 将根据条件过滤效果。
Find 方法的其他功能



  • 查询条件:你可以通过传递查询条件来限定查询的效果。例如,假如你想查找年事大于 30 的所有用户,可以这么写:
  1. db.Find(&users, "age > ?", 30)
复制代码
这个查询会返回所有年事大于 30 的用户。


  • 分页:Find 还支持分页查询。你可以通过 Limit 和 Offset 方法来实现分页查询。例如,查询前 10 条记载:
  1. db.Limit(10).Find(&users)
复制代码


  • 排序:你也可以通过 Order 方法来指定查询效果的排序方式。例如,按年事排序:
  1. db.Order("age desc").Find(&users)
复制代码


  • 返回记载数:Find 方法还会返回查询的效果,包罗查询到的记载数。假如没有记载,它会返回一个空的切片。
4.3 更新记载

在 GORM 中,更新记载是一个常见的操作。你可以通过 GORM 提供的几种方法来更新记载。以下将详细介绍 GORM 中更新记载的方式,包含基本更新、部分更新、批量更新等操作,并解释每种方法的具体用法和注意事项。
基本更新:db.Save 方法

db.Save 方法用于保存(或更新)布局体中的数据。假如布局体的主键已经存在,GORM 会实行 更新操作;假如主键不存在,GORM 会实行 插入操作(也称为 “upsert”)。因此,db.Save 不但实用于更新已有记载,也实用于插入新记载。
示例

  1. func main() {
  2.     var user User
  3.     db.First(&user, 1) // 查找主键为 1 的用户
  4.     user.Name = "Alice Updated" // 修改字段
  5.     user.Age = 30
  6.     db.Save(&user) // 更新记录
  7. }
复制代码


  • db.Save(&user) 会查抄 user 是否已有主键值(假设主键存在)。假如存在,它将实行更新操作,将 user 布局体中修改的字段更新到数据库中。
  • 假如主键不存在,它会将 user 插入到数据库中。
注意:



  • Save 会更新所有非零字段(即布局体中的字段假如是空值,可能不会被更新),并且会更新所有字段,纵然你没有显式修改某个字段。
  • 假如你希望只更新某些字段,应该利用 Updates 或 Update 方法。

更新部分字段:db.Updates 方法

db.Updates 方法允许你更新布局体中的 部分字段,而不是全部字段。它是一个更准确的更新方法,通常用于仅更新布局体中某些修改了的字段。
示例

  1. func main() {
  2.     var user User
  3.     db.First(&user, 1) // 查找主键为 1 的用户
  4.     db.Model(&user).Updates(User{Name: "Bob Updated", Age: 35})
  5. }
复制代码
在这个例子中:


  • db.Model(&user).Updates(User{Name: "Bob Updated", Age: 35}) 只会更新 user 布局体中的 Name 和 Age 字段。
  • db.Model(&user) 表明更新的是 user 布局体对应的数据库记载。
  • Updates 方法中的参数可以是一个布局体(如 User{Name: "Bob Updated"}),也可以是一个 map[string]interface{}(键是字段名,值是要更新的值)。
注意:



  • Updates 会忽略零值字段(如空字符串、零整数等),假如某个字段的值为零,它不会被更新。
  • db.Model(&user) 用于指定要更新的模型或表。
  • Updates 会将修改过的字段举行更新,但不会更新模型中未指定的字段。

单个字段更新:db.Update 方法

假如你只需要更新某个单独的字段,可以利用 db.Update 方法。该方法用于 更新单个字段,是 db.Updates 的简化版本,得当只更新单一字段的场景。
示例

  1. func main() {
  2.     var user User
  3.     db.First(&user, 1) // 查找主键为 1 的用户
  4.     db.Model(&user).Update("Age", 40) // 只更新 Age 字段
  5. }
复制代码


  • db.Model(&user).Update("Age", 40) 会将 Age 字段更新为 40,其他字段保持不变。
  • Update 方法实用于你只需要更新单个字段的情况。
注意:



  • Update 方法只更新指定的字段,不会影响其他字段。
  • 假如字段的值为零值,Update 也会更新该字段(没有 零值忽略 的机制)。

批量更新:db.UpdateColumn 和 db.UpdateColumns

GORM 还提供了 UpdateColumn 和 UpdateColumns 方法,主要用于 批量更新 字段。这些方法与 Update 方法雷同,但它们不会触发 GORM 的钩子(如 BeforeSave、AfterSave 等)。
UpdateColumn 示例

  1. db.Model(&user).UpdateColumn("Age", 45)
复制代码
UpdateColumn 不会触发 GORM 的 BeforeSave 和 AfterSave 钩子,因此实用于需要绕过这些钩子的情况。
UpdateColumns 示例

  1. db.Model(&user).UpdateColumns(map[string]interface{}{"Age": 50, "Name": "Charlie Updated"})
复制代码
UpdateColumns 会根据传入的字段举行批量更新。与 Update 不同,它不会触发模型的钩子。
注意:



  • 这两个方法直接更新字段,不会对字段的零值举行忽略。
  • 它们只举行 单字段的原子更新,不会涉及到多表关联等操作。

条件更新:db.Where 和 db.Updates

你可以在更新时通过 Where 方法指定更新的条件。Where 方法可以与 Updates 或 Update 一起利用,以便举行条件更新。
示例

  1. db.Model(&User{}).Where("age > ?", 30).Updates(User{Name: "Updated Name"})
复制代码


  • 这个示例中,Where("age > ?", 30) 限定了更新条件,只有年事大于 30 的用户才会被更新。
  • Updates(User{Name: "Updated Name"}) 更新所有符合条件的用户的 Name 字段。
注意:



  • Where 可以资助你构造复杂的更新条件,但也可以根据需要单独利用(例如,按 ID 更新某些记载)。

批量更新(多个记载)

你可以利用 db.Model() 方法和 db.Updates() 方法来批量更新多个记载。下面是一个批量更新的示例:
示例

  1. db.Model(&User{}).Where("age > ?", 30).Updates(User{Name: "Batch Update"})
复制代码


  • 这个例子会更新所有 age > 30 的用户,将它们的 Name 字段修改为 "Batch Update"。
注意:



  • Updates 会更新所有符合条件的记载,而不是只更新一个记载。

利用事务更新多个记载

假如你需要确保多个更新操作的原子性,可以将更新操作放入一个事务中。在 GORM 中,事务通过 db.Begin() 开始,db.Commit() 提交,db.Rollback() 回滚。
示例

  1. tx := db.Begin()
  2. // 执行多个更新操作
  3. tx.Model(&User{}).Where("age > ?", 30).Updates(User{Name: "Transactional Update"})
  4. tx.Model(&User{}).Where("name = ?", "Bob").Update("Age", 40)
  5. if err := tx.Commit().Error; err != nil {
  6.     tx.Rollback()
  7.     fmt.Println("Error:", err)
  8.     return
  9. }
复制代码


  • db.Begin() 开始一个事务。
  • tx.Commit() 提交事务,tx.Rollback() 在堕落时回滚事务,确保所有操作的原子性。
4.4 删除记载

删除记载可以利用 Delete 方法:
  1. func deleteUser(db *gorm.DB) {
  2.     var user User
  3.     db.First(&user, 1)  // 查找要删除的用户
  4.     // 删除用户
  5.     result := db.Delete(&user)
  6.     if result.Error != nil {
  7.         panic("Failed to delete user")
  8.     }
  9.     fmt.Println("User deleted:", user.ID)
  10. }
复制代码
5. 关系与关联查询

GORM 支持表之间的关系映射。比如,我们有 User 和 Post 之间的关系。一个用户可以有多个帖子,可以利用 has many 关系。
5.1 定义关联布局体

  1. type Post struct {
  2.     ID     uint
  3.     Title  string
  4.     Body   string
  5.     UserID uint  // 外键
  6.     User   User  // 关联的 User
  7. }
复制代码
5.2 关联查询

假设我们有 User 和 Post 两个表,你可以利用 Preload 来加载关联的 Post 数据。
  1. func getUserWithPosts(db *gorm.DB) {
  2.     var user User
  3.     db.Preload("Posts").First(&user, 1)
  4.     fmt.Println("User:", user.Name)
  5.     fmt.Println("Posts:", user.Posts)
  6. }
复制代码
5.3 创建关联记载

当你插入一个带有关联的记载时,可以利用 Create 方法来同时插入主表和从表数据:
  1. func createUserWithPosts(db *gorm.DB) {
  2.     user := User{Name: "Alice", Age: 28, Posts: []Post{
  3.         {Title: "Post 1", Body: "This is the first post"},
  4.         {Title: "Post 2", Body: "This is the second post"},
  5.     }}
  6.     db.Create(&user)
  7.     fmt.Println("User and Posts created:", user)
  8. }
复制代码
6. 事务

在 GORM 中,事务(Transaction) 是一个非常重要的概念,尤其是在需要确保多个数据库操作要么全部乐成,要么全部失败的情况下。事务可以或许保证操作的原子性、一致性、隔离性和持久性(即 ACID 特性)。假如在事务中的某个操作失败,事务可以回滚,使得数据库回到事务开始之前的状态。
事务(Transaction)是一组数据库操作,它们要么全部实行,要么在发生错误时全部不实行。事务在数据库操作中提供了 原子性(Atomicity)一致性(Consistency)隔离性(Isolation)持久性(Durability)(合称为ACID特性):


  • 原子性(Atomicity):事务中的所有操作要么都实行,要么都不实行。即事务是不可分割的整体。
  • 一致性(Consistency):事务实行前后,数据库的状态必须是一致的,符合数据库的完备性束缚。
  • 隔离性(Isolation):一个事务的实行不会被其他事务干扰。事务的实行是相互隔离的。
  • 持久性(Durability):一旦事务提交,其对数据库的更改是永世性的,不会丢失。
在 GORM 中,你可以通过 db.Begin() 来启动一个事务,利用 tx.Commit() 来提交事务,利用 tx.Rollback() 来回滚事务。以下是 GORM 中事务的常见用法。
6.1. 开始事务:db.Begin()

你可以通过 db.Begin() 启动一个事务。这个方法会返回一个事务对象(*gorm.DB 范例),通过这个对象你可以实行数据库操作。
  1. tx := db.Begin()  // 开始事务
复制代码


  • db.Begin() 会创建一个事务。
  • 返回的 tx 是事务对象,所有的数据库操作都应通过 tx 来实行,而不是直接利用 db。
6.2 实行事务中的操作

在事务中,你可以举行一系列的数据库操作。所有的操作都应该通过事务对象 tx 来实行,而不是直接通过 db 实行。
示例

  1. tx := db.Begin()  // 开始事务
  2. // 实行多个数据库操作if err := tx.Create(&user).Error; err != nil {    tx.Rollback()  // 操作失败,回滚事务    return err}if err := tx.Model(&user).Update("Age", 30).Error; err != nil {    tx.Rollback()  // 操作失败,回滚事务    return err}
复制代码


  • tx.Create(&user) 会在事务中插入一条记载。
  • tx.Model(&user).Update("Age", 30) 会在事务中更新该记载。
6.3 提交事务:tx.Commit()

当所有的操作都实行乐成时,可以调用 tx.Commit() 提交事务,将所有的变更永世保存到数据库。
  1. if err := tx.Commit().Error; err != nil {
  2.     tx.Rollback()  // 提交失败,回滚事务
  3.     return err
  4. }
复制代码


  • tx.Commit() 会提交事务,实行所有的操作并将它们持久化到数据库。
6.4 回滚事务:tx.Rollback()

假如在事务过程中遇到错误,应该调用 tx.Rollback() 来回滚事务。这样,所有在事务中实行的操作都会打消,数据库将规复到事务开始前的状态。
  1. if err := tx.Rollback().Error; err != nil {
  2.     fmt.Println("Error during rollback:", err)
  3.     return err
  4. }
复制代码


  • tx.Rollback() 会打消事务中的所有操作。
6.5 在事务中利用错误处理

通常,事务中的操作需要举行错误处理。只要有任何一项操作失败,应该调用 tx.Rollback() 举行回滚。
  1. tx := db.Begin()// 实行操作 1if err := tx.Create(&user).Error; err != nil {    tx.Rollback()  // 错误发生,回滚事务    return err}// 实行操作 2if err := tx.Model(&user).Update("Age", 30).Error; err != nil {    tx.Rollback()  // 错误发生,回滚事务    return err}// 所有操作乐成,提交事务if err := tx.Commit().Error; err != nil {
  2.     tx.Rollback()  // 提交失败,回滚事务
  3.     return err
  4. }
复制代码
6.6 事务中的多表操作

在事务中,你可以操作多个表,只要利用同一个事务对象 tx,所有的表操作都将在一个事务内完成。
示例:多表操作

  1. tx := db.Begin()
  2. // 插入用户表
  3. if err := tx.Create(&user).Error; err != nil {
  4.     tx.Rollback()
  5.     return err
  6. }
  7. // 更新订单表
  8. if err := tx.Model(&order).Update("Status", "Shipped").Error; err != nil {
  9.     tx.Rollback()
  10.     return err
  11. }
  12. // 提交事务
  13. if err := tx.Commit().Error; err != nil {
  14.     tx.Rollback()
  15.     return err
  16. }
复制代码


  • 这里插入了一个用户并更新了订单状态,所有操作都在同一个事务中举行。
6.7 事务的嵌套

GORM 不直接支持嵌套事务(即在一个事务中开启另一个事务)。但是,你可以通过手动管理事务嵌套。在嵌套事务中,只有最外层的事务会决定是否提交或回滚。
  1. tx := db.Begin()
  2. // 外部事务操作
  3. if err := tx.Create(&user).Error; err != nil {
  4.     tx.Rollback()
  5.     return err
  6. }
  7. nestedTx := tx.Begin()  // 开始嵌套事务
  8. // 嵌套事务操作
  9. if err := nestedTx.Model(&order).Update("Status", "Shipped").Error; err != nil {
  10.     nestedTx.Rollback()  // 嵌套事务回滚
  11.     tx.Rollback()        // 外部事务回滚
  12.     return err
  13. }
  14. nestedTx.Commit()  // 嵌套事务提交
  15. tx.Commit()  // 外部事务提交
复制代码


  • 上述代码演示了怎样在一个事务中手动开启一个嵌套事务。嵌套事务的提交和回滚会影响最外层事务。
6.8 事务中的并发题目

在事务中利用并发操作时,必须小心并发引起的 数据竞争死锁 题目。GORM 默认利用 隔离级别 为 ReadCommitted,你可以通过设置数据库的事务隔离级别来制止一些并发题目。
  1. tx := db.Begin().Set("gorm:query_option", "LOCK IN SHARE MODE")
  2. // 事务操作
复制代码
此时,LOCK IN SHARE MODE 会在查询时加锁,制止其他事务修改同一行数据,防止数据不一致。
总结

GORM 是一个功能强大且易于利用的 Go 语言 ORM 库,可以或许让开发者以面向对象的方式与数据库交互,减少了 SQL 语句的编写和管理的复杂度。它得当需要处理数据库的 Go 项目,特别是那些涉及大量数据操作、需要事务支持和多表关联的应用。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

正序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

玛卡巴卡的卡巴卡玛

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表