Golang

关注公众号 jb51net

关闭
首页 > 脚本专栏 > Golang > GO GORM 使用

GO中GORM 使用教程

作者:小青柑-

GORM是Go语言的ORM库,它将关系型数据库表与Go语言结构体映射,提供便捷的数据库操作,本文就来介绍有下GO中GORM 使用教程,具有一定的参考价值,感兴趣的可以了解一下

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

1. 安装 GORM

首先,你需要安装 GORM 库。打开终端并运行以下命令:

go get -u gorm.io/gorm
go get -u gorm.io/driver/sqlite  # 示例数据库,可以根据需求更换为其他数据库驱动

2. 创建基础结构体

ORM的一个核心概念是结构体,它代表数据库表的一个映射。例如,假设你有一个“用户”表,我们可以创建一个 User 结构体来表示它。

package main

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

type User struct {
    ID        uint   `gorm:"primaryKey"`  // 主键
    Name      string `gorm:"size:100"`    // 用户名字段,限制长度为100
    Age       uint   // 用户年龄
    CreatedAt time.Time  // 创建时间(GORM 会自动管理)
}

func main() {
    // 创建数据库连接
    db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{})
    if err != nil {
        panic("failed to connect to the database")
    }

    // 自动迁移:通过GORM根据结构体自动创建表
    db.AutoMigrate(&User{})
}

在 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 数据库的代码如下:

db, err := gorm.Open(mysql.Open("user:password@tcp(localhost:3306)/dbname"), &gorm.Config{})

4. 数据库操作

4.1 创建记录

你可以使用 Create 方法来插入数据。下面是如何插入一个新用户:

func createUser(db *gorm.DB) {
    user := User{Name: "John Doe", Age: 30}
    result := db.Create(&user)
    if result.Error != nil {
        panic("Failed to create user")
    }
    fmt.Println("User created:", user.ID)
}

代码中db.Create(&user) 使用 & 符号是因为我们要传递结构体 user 的 指针 给 Create 方法。具体来说,这样做有以下几个原因:

4.2 查询记录

GORM 提供了多种查询方式,可以通过结构体查询、条件查询等方式来获取数据。

获取单条记录

func getUser(db *gorm.DB) {
    var user User
    result := db.First(&user, 1)  // 查找 user 表中主键为 1 的记录,并将其填充到 user 结构体中
    if result.Error != nil {
        panic("User not found")
    }
    fmt.Println("User:", user)
}

db.First 是 GORM 提供的一个查询方法,用于从数据库中获取 第一条 满足条件的记录。它通常用于根据主键或其他条件查询数据。

db.First 的基本语法:

db.First(&model, conditions...)

如果查询成功,db.First 会把查询到的记录填充到 model 指针所指向的结构体里。如果没有找到记录,它会返回一个错误。

在 db.First(&user, 1) 中,&user 是指向 user 结构体的指针。这里传递指针是因为 GORM 要修改 user 结构体的值(即填充查询结果)。

获取多个记录

func getUsers(db *gorm.DB) {
    var users []User
    result := db.Find(&users)
    if result.Error != nil {
        panic("No users found")
    }
    fmt.Println("Users:", users)
}

db.Find 是 GORM 提供的查询方法之一,用于查找多个记录并将其存储到传入的切片结构体中。

db.Find(&users) 会从数据库中查找所有记录(或者根据传入的查询条件查找记录)并将它们填充到 users 切片中。查询的结果会是一个结构体的集合。Find 方法默认返回所有满足条件的记录。

Find 方法的其他功能

db.Find(&users, "age > ?", 30)

这个查询会返回所有年龄大于 30 的用户。

db.Limit(10).Find(&users)
db.Order("age desc").Find(&users)

4.3 更新记录

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

基本更新:db.Save 方法

db.Save 方法用于保存(或更新)结构体中的数据。如果结构体的主键已经存在,GORM 会执行 更新操作;如果主键不存在,GORM 会执行 插入操作(也称为 “upsert”)。因此,db.Save 不仅适用于更新已有记录,也适用于插入新记录。

示例

func main() {
    var user User
    db.First(&amp;user, 1) // 查找主键为 1 的用户

    user.Name = "Alice Updated" // 修改字段
    user.Age = 30

    db.Save(&amp;user) // 更新记录
}

注意:

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

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

示例

func main() {
    var user User
    db.First(&amp;user, 1) // 查找主键为 1 的用户

    db.Model(&amp;user).Updates(User{Name: "Bob Updated", Age: 35})
}

在这个例子中:

注意:

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

如果你只需要更新某个单独的字段,可以使用 db.Update 方法。该方法用于 更新单个字段,是 db.Updates 的简化版本,适合只更新单一字段的场景。

示例

func main() {
    var user User
    db.First(&user, 1) // 查找主键为 1 的用户

    db.Model(&user).Update("Age", 40) // 只更新 Age 字段
}

注意:

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

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

UpdateColumn 示例

db.Model(&user).UpdateColumn("Age", 45)

UpdateColumn 不会触发 GORM 的 BeforeSave 和 AfterSave 钩子,因此适用于需要绕过这些钩子的情况。

UpdateColumns 示例

db.Model(&user).UpdateColumns(map[string]interface{}{"Age": 50, "Name": "Charlie Updated"})

UpdateColumns 会根据传入的字段进行批量更新。与 Update 不同,它不会触发模型的钩子。

注意:

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

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

示例

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

注意:

批量更新(多个记录)

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

示例

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

注意:

使用事务更新多个记录

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

示例

tx := db.Begin()

// 执行多个更新操作
tx.Model(&User{}).Where("age > ?", 30).Updates(User{Name: "Transactional Update"})
tx.Model(&User{}).Where("name = ?", "Bob").Update("Age", 40)

if err := tx.Commit().Error; err != nil {
    tx.Rollback()
    fmt.Println("Error:", err)
    return
}

4.4 删除记录

删除记录可以使用 Delete 方法:

func deleteUser(db *gorm.DB) {
    var user User
    db.First(&user, 1)  // 查找要删除的用户

    // 删除用户
    result := db.Delete(&user)
    if result.Error != nil {
        panic("Failed to delete user")
    }
    fmt.Println("User deleted:", user.ID)
}

5. 关系与关联查询

GORM 支持表之间的关系映射。比如,我们有 User 和 Post 之间的关系。一个用户可以有多个帖子,可以使用 has many 关系。

5.1 定义关联结构体

type Post struct {
    ID     uint
    Title  string
    Body   string
    UserID uint  // 外键
    User   User  // 关联的 User
}

5.2 关联查询

假设我们有 User 和 Post 两个表,你可以使用 Preload 来加载关联的 Post 数据。

func getUserWithPosts(db *gorm.DB) {
    var user User
    db.Preload("Posts").First(&user, 1)
    fmt.Println("User:", user.Name)
    fmt.Println("Posts:", user.Posts)
}

5.3 创建关联记录

当你插入一个带有关联的记录时,可以使用 Create 方法来同时插入主表和从表数据:

func createUserWithPosts(db *gorm.DB) {
    user := User{Name: "Alice", Age: 28, Posts: []Post{
        {Title: "Post 1", Body: "This is the first post"},
        {Title: "Post 2", Body: "This is the second post"},
    }}
    db.Create(&user)
    fmt.Println("User and Posts created:", user)
}

6. 事务

在 GORM 中,事务(Transaction) 是一个非常重要的概念,尤其是在需要确保多个数据库操作要么全部成功,要么全部失败的情况下。事务能够保证操作的原子性、一致性、隔离性和持久性(即 ACID 特性)。如果在事务中的某个操作失败,事务可以回滚,使得数据库回到事务开始之前的状态。

事务(Transaction)是一组数据库操作,它们要么全部执行,要么在发生错误时全部不执行。事务在数据库操作中提供了 原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation) 和 持久性(Durability)(合称为ACID特性):

在 GORM 中,你可以通过 db.Begin() 来启动一个事务,使用 tx.Commit() 来提交事务,使用 tx.Rollback() 来回滚事务。以下是 GORM 中事务的常见用法。

6.1. 开始事务:db.Begin()

你可以通过 db.Begin() 启动一个事务。这个方法会返回一个事务对象(*gorm.DB 类型),通过这个对象你可以执行数据库操作。

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

6.2 执行事务中的操作

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

示例

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

// 执行多个数据库操作
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
}

6.3 提交事务:tx.Commit()

当所有的操作都执行成功时,可以调用 tx.Commit() 提交事务,将所有的变更永久保存到数据库。

if err := tx.Commit().Error; err != nil {
    tx.Rollback()  // 提交失败,回滚事务
    return err
}

6.4 回滚事务:tx.Rollback()

如果在事务过程中遇到错误,应该调用 tx.Rollback() 来回滚事务。这样,所有在事务中执行的操作都会撤销,数据库将恢复到事务开始前的状态。

if err := tx.Rollback().Error; err != nil {
    fmt.Println("Error during rollback:", err)
    return err
}

6.5 在事务中使用错误处理

通常,事务中的操作需要进行错误处理。只要有任何一项操作失败,应该调用 tx.Rollback() 进行回滚。

tx := db.Begin()

// 执行操作 1
if err := tx.Create(&user).Error; err != nil {
    tx.Rollback()  // 错误发生,回滚事务
    return err
}

// 执行操作 2
if err := tx.Model(&user).Update("Age", 30).Error; err != nil {
    tx.Rollback()  // 错误发生,回滚事务
    return err
}

// 所有操作成功,提交事务
if err := tx.Commit().Error; err != nil {
    tx.Rollback()  // 提交失败,回滚事务
    return err
}

6.6 事务中的多表操作

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

示例:多表操作

tx := db.Begin()

// 插入用户表
if err := tx.Create(&user).Error; err != nil {
    tx.Rollback()
    return err
}

// 更新订单表
if err := tx.Model(&order).Update("Status", "Shipped").Error; err != nil {
    tx.Rollback()
    return err
}

// 提交事务
if err := tx.Commit().Error; err != nil {
    tx.Rollback()
    return err
}

6.7 事务的嵌套

GORM 不直接支持嵌套事务(即在一个事务中开启另一个事务)。但是,你可以通过手动管理事务嵌套。在嵌套事务中,只有最外层的事务会决定是否提交或回滚。

tx := db.Begin()

// 外部事务操作
if err := tx.Create(&user).Error; err != nil {
    tx.Rollback()
    return err
}

nestedTx := tx.Begin()  // 开始嵌套事务

// 嵌套事务操作
if err := nestedTx.Model(&order).Update("Status", "Shipped").Error; err != nil {
    nestedTx.Rollback()  // 嵌套事务回滚
    tx.Rollback()        // 外部事务回滚
    return err
}

nestedTx.Commit()  // 嵌套事务提交

tx.Commit()  // 外部事务提交

6.8 事务中的并发问题

在事务中使用并发操作时,必须小心并发引起的 数据竞争 和 死锁 问题。GORM 默认使用 隔离级别 为 ReadCommitted,你可以通过配置数据库的事务隔离级别来避免一些并发问题。

tx := db.Begin().Set("gorm:query_option", "LOCK IN SHARE MODE")

// 事务操作

此时,LOCK IN SHARE MODE 会在查询时加锁,避免其他事务修改同一行数据,防止数据不一致。

总结

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

到此这篇关于GO中GORM 使用教程的文章就介绍到这了,更多相关GO GORM 使用内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

阅读全文