MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Go使用GORM进行数据库操作

2021-06-014.8k 阅读

Go 语言与 GORM 简介

Go 语言作为一门开源的编程语言,由 Google 开发并于 2009 年正式对外发布。它兼具高效的性能与简洁的语法,在网络编程、云计算、分布式系统等诸多领域都有着广泛的应用。Go 语言的并发模型基于 goroutine 和 channel,使得编写高并发程序变得相对容易,这也是其在现代软件开发中备受青睐的重要原因之一。

GORM 是 Go 语言中一款强大的对象关系映射(ORM)库。ORM 旨在将数据库中的表结构与面向对象编程语言中的对象进行映射,从而让开发者可以通过操作对象的方式来操作数据库,而无需编写大量的 SQL 语句。GORM 不仅提供了简洁的 API,还支持多种数据库,如 MySQL、PostgreSQL、SQLite 等,大大提高了数据库操作的效率和代码的可维护性。

安装与初始化 GORM

安装 GORM

在使用 GORM 之前,需要先将其安装到项目中。Go 语言使用 go get 命令来获取并安装依赖包。以安装 GORM 为例,可以在终端中执行以下命令:

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

上述命令中,-u 标志用于更新到最新版本。这里假设我们使用 MySQL 数据库,因此还需要安装 MySQL 驱动。如果使用其他数据库,如 PostgreSQL,则需安装相应的驱动,例如 gorm.io/driver/postgres

初始化 GORM

安装完成后,就可以在代码中初始化 GORM 连接。以下是一个简单的初始化示例,连接到 MySQL 数据库:

package main

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

func main() {
    dsn := "user:password@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
    db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
    if err != nil {
        panic("failed to connect database")
    }
    // 后续可以使用 db 进行数据库操作
}

在上述代码中,dsn(数据源名称)包含了连接数据库所需的信息,如用户名、密码、主机地址、端口、数据库名称以及一些连接选项。gorm.Open 函数用于打开数据库连接,第一个参数是数据库驱动的 Open 函数返回的 Driver,第二个参数是 gorm.Config 结构体指针,可以用于配置 GORM 的一些行为。如果连接失败,err 不为 nil,程序会通过 panic 终止并输出错误信息。

定义模型

在 GORM 中,模型是与数据库表对应的 Go 结构体。通过定义模型结构体,GORM 可以自动将结构体字段映射到数据库表的列。以下是一个简单的用户表模型定义示例:

package main

import (
    "gorm.io/gorm"
)

type User struct {
    gorm.Model
    Name  string
    Age   int
    Email string
}

在上述代码中,User 结构体嵌入了 gorm.Modelgorm.Model 包含了一些常用的字段,如 ID(主键)、CreatedAt(记录创建时间)、UpdatedAt(记录更新时间)和 DeletedAt(记录删除时间,软删除时使用)。此外,User 结构体还定义了 NameAgeEmail 三个自定义字段,这些字段将对应数据库表中的列。

结构体标签(Struct Tags)

GORM 使用结构体标签来进一步定制模型与数据库表之间的映射关系。以下是一些常用的结构体标签示例:

type Product struct {
    ID    uint `gorm:"primaryKey"`
    Name  string
    Price float64 `gorm:"type:decimal(10,2)"`
    Stock int     `gorm:"default:0"`
}
  • gorm:"primaryKey":指定 ID 字段为主键。
  • gorm:"type:decimal(10,2)":指定 Price 字段在数据库中的类型为 decimal(10, 2),表示最多 10 位数字,其中 2 位是小数。
  • gorm:"default:0":指定 Stock 字段的默认值为 0。

数据库迁移

数据库迁移是将模型结构同步到数据库中的过程。GORM 提供了方便的数据库迁移功能,可以自动创建、修改和删除数据库表。

创建表

使用 GORM 进行表创建非常简单。在初始化 GORM 连接后,可以使用 AutoMigrate 方法来自动迁移模型到数据库:

package main

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

type User struct {
    gorm.Model
    Name  string
    Age   int
    Email string
}

func main() {
    dsn := "user:password@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
    db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
    if err != nil {
        panic("failed to connect database")
    }

    err = db.AutoMigrate(&User{})
    if err != nil {
        panic("failed to migrate database")
    }
}

在上述代码中,db.AutoMigrate(&User{}) 会自动在数据库中创建 users 表(默认表名是结构体名的复数形式),表结构与 User 模型定义一致。如果表已经存在,AutoMigrate 不会覆盖已有的表结构,但会创建缺失的列。

修改表结构

当模型结构体发生变化时,可以再次调用 AutoMigrate 方法来更新数据库表结构。例如,如果在 User 模型中新增一个字段 Phone

type User struct {
    gorm.Model
    Name  string
    Age   int
    Email string
    Phone string
}

再次执行 db.AutoMigrate(&User{}),GORM 会检测到 User 模型的变化,并在数据库的 users 表中新增 phone 列。

删除表

如果要删除数据库表,可以使用 DropTable 方法:

err = db.Migrator().DropTable(&User{})
if err != nil {
    panic("failed to drop table")
}

上述代码会删除与 User 模型对应的数据库表。需要注意的是,删除表操作会永久删除表及其数据,使用时需谨慎。

创建记录

创建单个记录

使用 GORM 创建单个记录非常直观。以下是向 User 表中插入一条记录的示例:

package main

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

type User struct {
    gorm.Model
    Name  string
    Age   int
    Email string
}

func main() {
    dsn := "user:password@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
    db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
    if err != nil {
        panic("failed to connect database")
    }

    user := User{
        Name:  "John Doe",
        Age:   30,
        Email: "johndoe@example.com",
    }
    result := db.Create(&user)
    if result.Error != nil {
        panic("failed to create user")
    }
    // user.ID 会被赋值为插入记录的主键值
}

在上述代码中,首先创建一个 User 结构体实例 user,然后调用 db.Create(&user) 将该实例插入到数据库中。Create 方法返回一个 Result 结构体,通过检查 result.Error 可以判断插入操作是否成功。插入成功后,user.ID 会被 GORM 自动赋值为插入记录的主键值。

创建多个记录

GORM 也支持一次性插入多条记录。只需将多个结构体实例组成一个切片,然后传递给 Create 方法:

users := []User{
    {Name: "Alice", Age: 25, Email: "alice@example.com"},
    {Name: "Bob", Age: 35, Email: "bob@example.com"},
}
result := db.Create(users)
if result.Error != nil {
    panic("failed to create users")
}

上述代码会将 users 切片中的两个 User 实例一次性插入到数据库中。同样,可以通过 result.Error 判断插入操作是否成功。

查询记录

基本查询

GORM 提供了丰富的查询方法。以下是一些基本查询示例:

// 查询单个记录
var user User
db.First(&user, 1) // 根据主键 ID 查询,这里查询 ID 为 1 的用户
// 或者
db.Where("name =?", "John Doe").First(&user) // 根据条件查询,这里查询 name 为 John Doe 的用户

// 查询多个记录
var users []User
db.Find(&users) // 查询所有用户
// 或者
db.Where("age >?", 30).Find(&users) // 查询年龄大于 30 的用户

在上述代码中,First 方法用于查询满足条件的第一条记录,Find 方法用于查询满足条件的所有记录。Where 方法用于添加查询条件,条件中的占位符使用 ?

复杂查询

除了基本查询,GORM 还支持复杂的查询条件,如 ANDORLIKE 等。以下是一些示例:

// AND 条件
db.Where("age >? AND name LIKE?", 30, "J%").Find(&users)

// OR 条件
db.Where("age >? OR email =?", 30, "johndoe@example.com").Find(&users)

// 子查询
subQuery := db.Table("users").Select("AVG(age)").Where("active =?", true)
db.Where("age >?", subQuery).Find(&users)

上述代码展示了如何使用 ANDOR 组合条件,以及如何使用子查询。

排序与分页

在查询结果时,常常需要对结果进行排序和分页。GORM 提供了相应的方法来实现这些功能:

// 排序
db.Order("age desc").Find(&users) // 按年龄降序排序

// 分页
limit := 10
offset := 0
db.Limit(limit).Offset(offset).Find(&users) // 每页 10 条记录,从第 0 条开始

Order 方法用于指定排序字段和排序方式,Limit 方法用于指定每页返回的记录数,Offset 方法用于指定偏移量。

更新记录

更新单个字段

GORM 允许方便地更新数据库中的记录。以下是更新单个字段的示例:

var user User
db.First(&user, 1) // 先查询出要更新的记录
user.Age = 31
db.Save(&user) // 保存更新后的记录

在上述代码中,首先查询出 ID 为 1 的用户,然后修改其 Age 字段的值,最后调用 db.Save(&user) 方法将更新保存到数据库。Save 方法会自动检测结构体字段的变化并更新到数据库。

更新多个字段

如果要更新多个字段,可以在查询出记录后直接修改多个字段的值,然后调用 Save 方法:

var user User
db.First(&user, 1)
user.Age = 32
user.Email = "newemail@example.com"
db.Save(&user)

上述代码会同时更新 AgeEmail 两个字段。

批量更新

除了单个记录更新,GORM 还支持批量更新。以下是批量更新年龄大于 30 的用户的邮箱的示例:

db.Model(&User{}).Where("age >?", 30).Update("email", "newemail@example.com")

在上述代码中,db.Model(&User{}) 表示操作 User 模型对应的表,Where 方法指定更新条件,Update 方法指定要更新的字段和值。

删除记录

删除单个记录

删除单个记录可以先查询出该记录,然后调用 Delete 方法:

var user User
db.First(&user, 1)
db.Delete(&user)

上述代码会先查询出 ID 为 1 的用户,然后将其从数据库中删除。

批量删除

批量删除与批量更新类似,通过 Where 方法指定删除条件,然后调用 Delete 方法:

db.Where("age >?", 30).Delete(&User{})

上述代码会删除年龄大于 30 的所有用户记录。

软删除

GORM 支持软删除功能,通过在模型结构体中嵌入 gorm.Model,其中的 DeletedAt 字段会被用于软删除。当调用 Delete 方法时,记录不会真正从数据库中删除,而是将 DeletedAt 字段设置为当前时间。

var user User
db.First(&user, 1)
db.Delete(&user)

软删除后,使用普通的查询方法将不会查询到该记录。如果要查询软删除的记录,可以使用 Unscoped 方法:

var deletedUser User
db.Unscoped().First(&deletedUser, 1)

上述代码会查询出被软删除的 ID 为 1 的用户记录。

事务处理

在数据库操作中,事务用于确保一组操作要么全部成功,要么全部失败。GORM 提供了简单易用的事务处理功能。以下是一个简单的转账事务示例:

func transfer(db *gorm.DB, fromUserID, toUserID uint, amount float64) error {
    return db.Transaction(func(tx *gorm.DB) error {
        // 扣除转账用户的金额
        var fromUser User
        if err := tx.First(&fromUser, fromUserID).Error; err != nil {
            return err
        }
        if fromUser.Balance < amount {
            return errors.New("insufficient balance")
        }
        fromUser.Balance -= amount
        if err := tx.Save(&fromUser).Error; err != nil {
            return err
        }

        // 增加收款用户的金额
        var toUser User
        if err := tx.First(&toUser, toUserID).Error; err != nil {
            return err
        }
        toUser.Balance += amount
        if err := tx.Save(&toUser).Error; err != nil {
            return err
        }

        return nil
    })
}

在上述代码中,db.Transaction 方法接受一个函数作为参数,该函数内部包含了需要在事务中执行的操作。如果函数中的任何一个操作返回错误,整个事务会回滚;如果函数正常返回,则事务提交。

关联关系

在实际应用中,数据库表之间常常存在各种关联关系,如一对一、一对多、多对多等。GORM 对这些关联关系提供了很好的支持。

一对一关联

假设我们有 UserProfile 两个模型,一个用户对应一个个人资料,这是典型的一对一关联。

type User struct {
    gorm.Model
    Name    string
    Profile Profile
}

type Profile struct {
    gorm.Model
    Bio     string
    UserID  uint
}

在上述代码中,User 结构体包含一个 Profile 类型的字段,Profile 结构体通过 UserID 字段与 User 建立关联。

一对多关联

UserOrder 为例,一个用户可以有多个订单,这是一对多关联。

type User struct {
    gorm.Model
    Name    string
    Orders  []Order
}

type Order struct {
    gorm.Model
    Amount  float64
    UserID  uint
}

User 结构体中的 Orders 字段是一个 Order 类型的切片,表示一个用户可以有多个订单。Order 结构体通过 UserID 字段与 User 建立关联。

多对多关联

假设我们有 UserRole 两个模型,一个用户可以有多个角色,一个角色可以被多个用户拥有,这是多对多关联。需要一个中间表来存储这种关系。

type User struct {
    gorm.Model
    Name    string
    Roles   []Role `gorm:"many2many:user_roles"`
}

type Role struct {
    gorm.Model
    Name    string
    Users   []User `gorm:"many2many:user_roles"`
}

在上述代码中,UserRole 结构体通过 many2many 标签指定中间表名为 user_roles

自定义 SQL 语句

虽然 GORM 提供了丰富的高级 API 来操作数据库,但在某些情况下,可能需要执行自定义的 SQL 语句。GORM 允许通过 Raw 方法来执行原生 SQL。

执行查询语句

以下是执行自定义查询语句的示例:

var users []User
db.Raw("SELECT * FROM users WHERE age >?", 30).Scan(&users)

在上述代码中,db.Raw 方法用于执行原生 SQL 查询语句,Scan 方法用于将查询结果扫描到 users 切片中。

执行更新和删除语句

执行自定义的更新和删除语句同样可以使用 Raw 方法:

// 更新语句
db.Exec("UPDATE users SET age = age + 1 WHERE age >?", 30)

// 删除语句
db.Exec("DELETE FROM users WHERE age >?", 30)

Exec 方法用于执行非查询类型的 SQL 语句,如 UPDATEDELETE 等。

通过以上对 GORM 在 Go 语言中进行数据库操作的详细介绍,相信读者已经对如何使用 GORM 进行高效的数据库开发有了全面的了解。无论是简单的增删改查,还是复杂的事务处理、关联关系管理,GORM 都提供了强大而简洁的解决方案,帮助开发者提高开发效率,降低代码复杂度。在实际项目中,开发者可以根据具体需求灵活运用 GORM 的各种功能,构建稳定、高效的数据库应用程序。