使用 GORM(Go 的 ORM 库)连接数据库,并实现增删改查操作,把实现过程整理成文章 | 青训营

360 阅读10分钟

ORM及GORM介绍

1. ORM

当谈论到数据库和应用程序之间的交互时,ORM(对象关系映射)是一个重要的概念。ORM 是一种编程技术,用于将对象模型和关系数据库之间建立映射。ORM 的主要目标是简化数据库操作和数据访问层的开发,使开发人员能够专注于业务逻辑而不是与数据库交互的细节。通过使用 ORM,开发人员可以使用类和对象来表示数据库中的表和记录,从而以面向对象的方式进行增删改查操作。

2. GORM

GORM 是 Go 语言中一个流行的 ORM 库,用于与关系型数据库进行交互。GORM 提供了许多便捷的功能,使得在 Go 语言中使用数据库变得更加容易。下面是 GORM 提供的一些主要功能:

  • 数据模型定义:开发人员可以通过定义结构体来表示数据库中的表,并使用 GORM 提供的标签将结构体字段与数据库表的列进行映射。
  • 数据库迁移:GORM 支持数据库迁移,即通过代码定义数据库表结构的变化,然后自动将这些变化应用到数据库中。
  • CRUD 操作:GORM 提供了方便的方法来执行数据库的增删改查操作,如 Create、Read、Update 和 Delete 等。
  • 关联关系:GORM 支持定义表之间的关联关系,如一对一、一对多和多对多等,并能够方便地进行关联查询。
  • 钩子函数:GORM 允许开发人员定义在数据操作前后触发的钩子函数,用于处理特定的逻辑。
  • 事务支持:GORM 支持事务操作,确保多个数据库操作要么全部成功,要么全部回滚。
  • 查询构造器:GORM 提供了强大的查询构造器,允许开发人员使用链式调用来构建复杂的查询条件。

GORM安装

GORM 是 Go 语言中的一个第三方库,因此在使用之前,需要通过 Go 的包管理工具来安装 GORM 包。

go get -u gorm.io/gorm

在 Go 代码中,导入 GORM 包:

import "gorm.io/gorm"

GORM连接数据库(MySQL、SQLite)

1. MySQL

  • 安装 MySQL 的驱动程序
go get -u gorm.io/driver/mysql
  • 安装并启动 MySQL 数据库

确保已经安装了 MySQL 数据库,安装教程可参考其他文章

  • 连接 MySQL 数据库
package main

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

func main() {
    // 定义 MySQL 数据库连接字符串
    dsn := "user:password@tcp(localhost:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"

    // 使用 gorm.Open() 连接 MySQL 数据库
    db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
    if err != nil {
        log.Fatal("Failed to connect to database:", err)
    }

    // 现在可以使用 db 变量进行数据库操作了
    // 例如:db.AutoMigrate(&YourModel{})
}

在连接 MySQL 时,参数如下:

  • user: 数据库用户名,用于登录数据库的用户名。
  • password: 数据库用户对应的密码,用于登录数据库的密码。
  • tcp(localhost:3306): 数据库的网络地址和端口号。在这个例子中,使用的是 TCP 连接方式,MySQL 服务器在本地(localhost)的 3306 端口上监听连接。
  • dbname: 数据库名称,要连接的具体数据库名。
  • charset=utf8mb4: 字符集设置。在这里设置为 utf8mb4,用于支持更广泛的 Unicode 字符集,包括 emoji 表情等。
  • parseTime=True: 这个参数用于告诉 GORM 在将数据库中的日期时间字段解析为 Go 中的时间类型时,是否启用自动解析功能。
  • loc=Local: 设置时区。在这里设置为 Local,表示使用本地时区。

2. SQLite

  • 安装 MySQL 的驱动程序
go get -u gorm.io/driver/sqlite
  • 安装 SQLite 数据库

SQLite 是一个轻量级的嵌入式数据库,它不需要独立的安装过程,只需要在项目中引入相关的库,连接数据库时指定 SQLite 数据库文件路径即可。

  • 连接 SQLite 数据库
package main

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

func main() {
    // 定义 SQLite 数据库文件路径
    dbPath := "my-database.db"

    // 使用 gorm.Open() 连接 SQLite 数据库
    db, err := gorm.Open(sqlite.Open(dbPath), &gorm.Config{})
    if err != nil {
        log.Fatal("Failed to connect to database:", err)
    }

    // 现在可以使用 db 变量进行数据库操作了
    // 例如:db.AutoMigrate(&YourModel{})
}

单表代码示例:GORM连接 SQLite 数据库,实现增删改查

package main

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

// User 是数据库表的结构体映射
type User struct {
    ID         uint      `gorm:"primaryKey"`
    Username   string    `gorm:"not null"`
    Age        int
    Registered time.Time
}

func main() {
    // 连接 SQLite 数据库
    db, err := gorm.Open(sqlite.Open("my-database.db"), &gorm.Config{})
    if err != nil {
            panic("连接数据库失败")
    }

    // 自动创建表结构
    err = db.AutoMigrate(&User{})
    if err != nil {
            panic("自动迁移表失败")
    }

    // 增加用户记录
    user1 := User{Username: "user_name1", Age: 25, Registered: time.Now()}
    user2 := User{Username: "user_name1", Age: 28, Registered: time.Now()}
    user3 := User{Username: "user_name3", Age: 30, Registered: time.Now()}
    user4 := User{Username: "user_name4", Age: 18, Registered: time.Now()}
    db.Create(&user1)
    db.Create(&user2)
    db.Create(&user3)
    db.Create(&user4)


    //***************查询*******************
    // 查询单个用户:名为 "user_name1" 记录
    var user User
    db.First(&user, "username = ?", "user_name1")
    fmt.Println("User 1:", user)

    // 条件查询:用户名为 "user_name1" 的记录
    var usersWithCondition1 []User
    db.Where("username = ?", "user_name1").Find(&usersWithCondition1)
    fmt.Println("User with username user_name1:", usersWithCondition1)

    // 条件查询: age 在 25 到 30 之间的用户记录中的 id 和 registered 字段
    var usersWithCondition2 []User
    db.Select("id, registered").Where("age BETWEEN ? AND ?", 25, 30).Find(&usersWithCondition2)
    fmt.Println("Users with age between 25 and 30:", usersWithCondition2)

    // 查询 age 在 20 到 30 之间的用户记录,并按id降序排序,只返回前两条记录
    var usersWithCondition3 []User
    db.Where("age BETWEEN ? AND ?", 20, 30).Order("id desc").Limit(2).Find(&usersWithCondition3)
    fmt.Println("Users with age between 20 and 30, ordered by id desc, limit 2:", usersWithCondition3)

    // 查询所有用户记录
    var users []User
    db.Find(&users)
    fmt.Println("All users:", users)
    //***************查询*******************


    //***************更新*******************
    // Updates更新用户名为 "user_name3" 的记录的 age 字段为 40
    db.Model(&User{}).Where("username = ?", "user_name3").Updates(User{Age: 40})

    // Save更新记录的 age 字段为 50,先查询用户名为 "user_name4" 的记录
    var user_save User
    db.First(&user_save, "username = ?", "user_name4")
    user_save.Age = 50
    db.Save(&user_save)

    // 输出更新后所有用户记录
    db.Find(&users)
    fmt.Println("After Update, All users:", users)
    //***************更新*******************


    //***************删除*******************
    // 删除 age 为 40 的记录
    db.Where("age = ?", 40).Delete(&User{})

    // 查询 age 为 28 的记录并删除
    var users_delete []User
    db.Where("age = ?", 28).Find(&users_delete)
    db.Delete(&users_delete)

    // 输出更新后所有用户记录
    db.Find(&users)
    fmt.Println("After Delete, All users:", users)
    //***************删除*******************
}

运行结果:

image.png

标签:

  • gorm:"primaryKey":在数据库表中,主键是用于唯一标识每个记录的字段。在 GORM 中,通过在结构体字段上添加 gorm:"primaryKey" 标签,你可以指定该字段作为数据库表的主键。对于大多数情况下,定义主键是很有用的。如果你不指定主键,GORM 会默认使用名为 "ID" 的字段作为主键(如果存在的话)。但如果你的表结构中没有 "ID" 字段,那么你就需要通过 gorm:"primaryKey" 标签来显式指定主键。
  • unique: 表示该字段在数据库表中是唯一的,即每条记录在此字段上的值必须是唯一的,不能重复。
  • not null: 表示该字段在数据库表中不允许为空,即每条记录在此字段上必须有一个非空的值。
  • autoIncrement: 定义字段为自增长。
  • index: 定义字段为索引,可以提高查询效率。
  • default: 定义字段的默认值。
  • size:50: 定义字段的长度限制。
  • column: 定义字段在数据库表中的列名。
  • type: 定义字段的数据库类型。

其他:

  • 在 GORM 中,如果你查询数据时没有选择某个字段,那么这个字段会保持为零值。

多表代码示例:

package main

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

// 定义用户表结构
type User struct {
    ID              uint   `gorm:"primaryKey"`
    Name            string // 用户姓名
    Age             int    // 年龄
    RegistrationDate string // 注册日期
    Orders          []Order `gorm:"foreignKey:UserID"` // 关联到订单表
}

// 定义订单表结构
type Order struct {
    ID     uint   `gorm:"primaryKey"`
    UserID uint   // 用户ID(关联到用户表的ID)
    Price  float64 // 订单价格
}

func main() {
    // 连接数据库
    db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
    if err != nil {
        panic("连接数据库失败")
    }

    // 自动迁移模式,创建表
    db.AutoMigrate(&User{}, &Order{})

    // 添加一些测试数据
    users := []User{
        {ID: 1, Name: "Alice", Age: 25, RegistrationDate: "2023-08-02"},
        {ID: 2, Name: "Bob", Age: 30, RegistrationDate: "2023-08-01"},
        {ID: 3, Name: "Charlie", Age: 22, RegistrationDate: "2023-07-31"},
    }

    orders := []Order{
        {ID: 1, UserID: 1, Price: 100},
        {ID: 2, UserID: 1, Price: 150},
        {ID: 3, UserID: 2, Price: 200},
        {ID: 4, UserID: 3, Price: 50},
    }

    // 创建记录
    db.Create(&users)
    db.Create(&orders)

    // 多表查询“用户年龄在20-30的用户ID、用户姓名、订单价格”
    var results []struct {
        UserID   uint
        UserName string
        Price    float64
    }

    // 使用GORM的原始SQL进行多表查询
    // 选择users表的ID和name字段,以及orders表的price字段
    // 在users表和orders表之间进行JOIN操作,使用users表的ID和orders表的user_id字段进行关联
    // 使用WHERE子句筛选年龄在20到30之间的用户
    db.Table("users").
        Select("users.id as user_id, users.name as user_name, orders.price as price").
        Joins("JOIN orders ON users.id = orders.user_id").
        Where("users.age BETWEEN ? AND ?", 25, 30).
        Scan(&results)

    // 打印结果
    for _, result := range results {
        fmt.Printf("UserID: %d, UserName: %s, Price: %.2f\n", result.UserID, result.UserName, result.Price)
    }
}

运行结果:

image.png

标签:

  • foreignKey:UserID 表示 OrdersUser 结构体中的一个切片字段,用于表示一个用户拥有多个订单。UserID 是关联表的外键字段,它指明了 Orders 切片中的每个订单所属的用户。
  • 在 GORM 中,如果没有定义 foreignKey,默认情况下,GORM 会使用关联表的类型名称和主键字段名称来生成外键字段的名称。但通过明确指定 foreignKey,你可以自定义外键字段的名称,使其更符合你的业务逻辑。

注:

  • db.Table("users"):使用Table方法指定要操作的数据库表为名为 "users" 的表。这表明接下来的查询操作将在 "users" 表上执行。
  • .Select("users.id as user_id, users.name as user_name, orders.price as price"):使用Select方法选择我们想要获取的字段。这里我们将 "users" 表的 "id" 字段映射为 "user_id","name" 字段映射为 "user_name",并将 "orders" 表的 "price" 字段映射为 "price"。这样在查询结果中,字段名将按照我们指定的新名称返回。
  • .Joins("JOIN orders ON users.id = orders.user_id"):使用Joins方法连接 "users" 表和 "orders" 表。这里我们通过 "users" 表的 "id" 字段与 "orders" 表的 "user_id" 字段进行关联(JOIN操作)。这样,我们可以根据用户的ID在两个表中进行匹配,将用户表和订单表关联在一起。
  • .Where("users.age BETWEEN ? AND ?", 25, 30):使用Where方法添加查询条件,筛选出年龄在25到30岁之间的用户。这个条件会在查询结果中只返回符合条件的用户数据。
  • .Scan(&results):执行查询并将结果扫描到指定的变量 results 中。results 是一个切片,用于存储查询结果的结构体。
  • 在GORM中,结构体字段的小写和大写有特定的含义。当字段名是小写字母开头时,表示该字段是私有字段(private field),仅在定义它的包内可见。而当字段名是大写字母开头时,表示该字段是公有字段(public field),可以被其他包访问。
  • 在GORM的Select方法中,使用小写字段名是因为在进行数据库查询时,GORM使用的是反射机制来解析结构体的字段。在这个过程中,它只能访问到公有字段,无法访问到私有字段。因此,如果想在查询结果中包含某个字段,该字段必须是公有字段,以便GORM能够正确地找到和解析它。
  • 在例子中,User结构体中的ID字段是大写字母开头的公有字段,所以GORM可以正确地访问和映射它。然而,我们在Select方法中使用了小写字段名"users.id",这是因为在数据库查询中,我们需要指定表名和字段名,而数据库表名和字段名一般都是小写的。所以,虽然在GORM的结构体定义中使用了大写字母开头的字段名,但在查询时,我们需要使用对应的小写字段名来指定表名和字段名,以确保查询操作能够正确地执行。
  • 在GORM中,如果字段名是驼峰式命名(如UserID),则在进行多表查询时,会将其转换为下划线式命名(如user_id)来匹配数据库表的列名。这是因为在不同的命名风格之间进行映射可以简化数据库字段和Go结构体字段之间的转换。
  • db.Table("users") 表示我们要在名为 "users" 的数据库表上执行操作。如果我们使用 User 结构体定义,GORM 默认情况下会将其映射到名为 "users" 的数据库表,因为 "User" 结构体的复数形式通常为 "users"。