使用 GORM(Go 的 ORM 库)连接数据库| 豆包MarsCode AI 刷题

621 阅读7分钟

1.安装配置

创建一个Go项目用来学习使用GORM,并初始化Go模块。

  1. 在项目根目录下,执行如下命令安装GORM: go get -u gorm.io/gorm

    1732440478467.png

  2. 同样在项目根目录下,执行如下命令安装MySQL 的驱动:

    go get -u gorm.io/driver/mysql

    1732445578631.png

    GORM目前支持MySQL、SQLServer、PostgreSQL、SQLite

    注意:在Windows系统上,安装SQLite来连接Mysql数据库,可以安装如下驱动,但是gorm.io/driver/sqlite不可以会报没有CGO错误

    • 使用 modernc.org/sqlite 作为 SQLite 的 Go 驱动。这是一个纯 Go 编写的 SQLite 库,不需要 CGO 支持。安装命令:go get modernc.org/sqlite
  3. 快速启动一个GORM项目

    注意:要在管理员身份下运行该代码,否则没有权限访问数据库

    代码如下:

    package main
    
    import (
    	"gorm.io/driver/mysql"
    	"gorm.io/gorm"
    )
    
    // User结构体定义,对应数据库中的表
    type User struct {
    	gorm.Model
    	Name string
    	Age  int
    }
    
    func main() {
    	// 连接字符串,根据实际情况修改用户名、密码、主机和数据库名
    	dsn := "username:password@tcp(127.0.0.1:3306)/gormstudy?charset=utf8mb4&parseTime=True&loc=Local"
    	// 打开数据库连接
    	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
    	if err != nil {
    		panic("连接数据库失败: " + err.Error())
    	}
    
    	// 自动迁移,创建User表(如果不存在)
    	db.AutoMigrate(&User{})
    
    	// 创建一个新用户
    	newUser := User{Name: "John", Age: 30}
    	result := db.Create(&newUser)
    	if result.Error != nil {
    		panic("插入数据失败: " + result.Error.Error())
    	}
    
    	// 查询所有用户
    	var users []User
    	db.Find(&users)
    	for _, user := range users {
    		println(user.Name, user.Age)
    	}
    }
    
    

    运行结果: 1732459536592.png

    GORM 通过将 Go 结构体(Go structs) 映射到数据库表来简化数据库交互。 了解如何在GORM中定义模型,是充分利用GORM全部功能的基础。

2.模型定义

2.1 GORM的约定

2.1.1 主键

  1. 使用 ID 作为主键

    默认情况下,GORM 会使用 ID 作为表的主键。

type User struct {
  ID   string // 默认情况下,名为 `ID` 的字段会作为表的主键
  Name string
}
  1. 通过标签 primaryKey 将其它字段设为主键
// 将 `UUID` 设为主键
type Animal struct {
  ID     int64
  UUID   string `gorm:"primaryKey"`
  Name   string
  Age    int64
}

2.1.2 表名

默认情况下,GORM将结构体名称转换为snake_case并为表名加上复数形式。例如,一个User结构体在数据库中的表名变为users

TableName

可以实现 Tabler 接口来更改默认表名,例如:

type Tabler interface {
    TableName() string
}

// TableName 会将 User 的表名重写为 `profiles`
func (User) TableName() string {
  return "profiles"
}

2.1.3 列名

GORM自动将结构体字段名称转为snake_case作为数据库中的列名

type User struct {
  ID        uint      // 列名是 `id`
  Name      string    // 列名是 `name`
  Birthday  time.Time // 列名是 `birthday`
  CreatedAt time.Time // 列名是 `created_at`
}

可以使用 column 标签或 命名策略 来覆盖列名

type Animal struct {
  AnimalID int64     `gorm:"column:beast_id"`         // 将列名设为 `beast_id`
  Birthday time.Time `gorm:"column:day_of_the_beast"` // 将列名设为 `day_of_the_beast`
  Age      int64     `gorm:"column:age_of_the_beast"` // 将列名设为 `age_of_the_beast`
}

2.1.4 时间戳字段

GORM使用字段CreateAtUpdatedAt来自动跟踪记录的创建和更新时间

2.2 gorm.Model

GORM提供了一个预定义的结构体,名为gorm.Model,其中包含常用字段:

// gorm.Model 的定义
type Model struct {
  ID        uint           `gorm:"primaryKey"`
  CreatedAt time.Time
  UpdatedAt time.Time
  DeletedAt gorm.DeletedAt `gorm:"index"`
}
  • 将其嵌入在结构体中: 可以直接在结构体中嵌入 gorm.Model ,以便自动包含这些字段。 这对于在不同模型之间保持一致性并利用GORM内置的约定非常有用。
  • 包含的字段
    • ID :每个记录的唯一标识符(主键)。
    • CreatedAt :在创建记录时自动设置为当前时间。
    • UpdatedAt:每当记录更新时,自动更新为当前时间。
    • DeletedAt:用于软删除(将记录标记为已删除,而实际上并未从数据库中删除)。

2.3 使用默认值

通过default标签为字段定义默认值

type User struct{
    ID 		int64
    Name 	string	`gorm:"default:ctc"`
    Age 	int64	`gorm:"defualt:18"`
}

3.连接到数据库

MySQL

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

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

4.创建

创建记录

p := &Product{Code: "D42"}
res := db.Create(p)//通过数据的指针来创建

fmt.Println(res.Error) //获取err
fmt.Println(res.RowsAffected) //返回插入记录的条数
fmt.Println(p.ID)      //返回输入数据的ID

还可以使用 Create() 创建多项记录:

//创建多条数据
products := []*Product{
    {Code: "D41"},
    {Code: "D42"},
    {Code: "D43"},
}

res = db.Create(products)
fmt.Println(res.Error)
for _, p := range products {
    fmt.Println(p.ID)
}

运行结果:

1732459456462.png

注意: 在GORM中增删改查函数,如Create(),这些都是FinishAPI后面的where操作是不会执行的,因为在增删改查函数前已经将语句提交到数据库了

根据 Map 创建

GORM支持通过 map[string]interface{}[]map[string]interface{}{}来创建记录。

//根据map创建记录一条记录
res := db.Model(&Product{}).Create(map[string]interface{}{
    "Code":  "F41",
    "Price": 1000,
})

if res.Error != nil {
    panic("map插入数据失败: " + res.Error.Error())
}

//根据map创建多条记录
res = db.Model(&Product{}).Create([]map[string]interface{}{
    {"Code": "F51"},
    {"Code": "F52"},
})

if res.Error != nil {
    panic("map批量插入数据失败: " + res.Error.Error())
}

运行结果:

1732459369695.png

使用Upsert

使用clause.OnConflict处理数据冲突

//以不处理冲突为例,创建一条数据
p := &Product{Code: "042", ID: 1}
db.Clauses(clause.OnConflict{DoNothing: true}).Create(&p)

5.查询

检索单个对象

GORM 提供了 FirstTakeLast 方法,以便从数据库中检索单个对象。当查询数据库时它添加了 LIMIT 1 条件,且没有找到记录时,它会返回 ErrRecordNotFound 错误

// 获取一条记录,没有指定排序字段
Takep := &Product{}
db.Take(Takep)
fmt.Printf("Take:%+v\n", Takep) // SELECT * FROM products LIMIT 1;

//获取最后一条记录(主键降序)
Lastp := &Product{}
db.Last(Lastp)
fmt.Printf("Last:%+v\n", Lastp) // SELECT * FROM products ORDER BY id DESC LIMIT 1;

注意: First 的使用踩坑 使用 First 时,需要注意查询不到数据会返回 ErrRecordNotFound. 使用 Find 查询多条数据,查询不到数 据不会返回错误。

运行结果:

1732462709925.png

注意: 当使用结构作为条件查询时,GORM只会查询非零值字段。这意味着如果您的字段值为 0、"、 false 或其他零值,该字段不会被用于构建查询条件,使用Map来构建查询条件。因为Go语言中的零值初始化机制,所以不知道是用户设置的,还是未被初始化

6.更新

更新单列

当使用 Update 更新单列时,需要有一些条件,否则将会引起ErrMissingWhereClause 错误

db.Model(&Product{}).Where("code =?", "D42").Update("price", 2000)

运行结果:

1732463387839.png

更新多列

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

db.Model(&Product{}).Where("code =?", "F52").Updates(map[string]interface{}{"code": "F62", "price": 1200})

运行结果:

1732464120945.png

7.删除

物理删除

删除单条数据

根据主键删除

GORM 允许通过主键(可以是复合主键)和内联条件来删除对象,它可以使用数字,代码如下:

db.Delete(&Product{}, 10)
// DELETE FROM products WHERE id = 10;

db.Delete(&Product{}, "10")
// DELETE FROM products WHERE id = 10;

db.Delete(&Product{}, []int{1,2,3})
// DELETE FROM products WHERE id IN (1,2,3);

运行结果:

1732465576845.png

根据条件删除

// 带额外条件的删除
db.Where("Code = ?", "D43").Delete(&product)

运行结果:

1732465864614.png

批量删除

如果指定的值不包括主属性,那么 GORM 会执行批量删除,它将删除所有匹配的记录

db.Where("Code LIKE ?", "F%").Delete(&Product{})

db.Delete(&Product{}, "products LIKE ?", "F%")

运行结果:

1732467048301.png

可以将一个主键切片传递给Delete 方法,以便更高效的删除数据量大的记录

var products = []Product{{ID: 6}, {ID: 7}}
db.Delete(&products)
// DELETE FROM products WHERE id IN (1,2,3);

db.Delete(&products, "Code LIKE ?", "D%")
// DELETE FROM products WHERE name LIKE "D%" AND id IN (1,2,3);

软删除

如果模型包含了 gorm.DeletedAt字段(该字段也被包含在gorm.Model中),那么该模型将会自动获得软删除的能力!

db.Delete(&user)

// Batch Delete
db.Where("age = ?", 30).Delete(&User{})
// UPDATE users SET deleted_at="2013-10-29 10:23" WHERE age = 30;

// Soft deleted records will be ignored when querying
db.Where("age = 30").Find(&user)
// SELECT * FROM users WHERE age = 30 AND deleted_at IS NULL;