GORM 数据库操作

GORM 数据库操作速查手册

GORM 是 Go 语言中一个功能强大的 ORM (Object-Relational Mapping) 库。它提供了简洁的 API 来操作数据库,支持多种数据库(如 MySQL, PostgreSQL, SQLite, SQL Server 等)。

本文档将介绍 GORM 的常用操作:插入 (Create)、查询 (Retrieve)、更新 (Update) 和删除 (Delete),并演示如何将查询结果赋值给 Go 语言的变量。

准备工作

在开始之前,确保你已经安装了 GORM 和相应的数据库驱动。

1
2
go get -u gorm.io/gorm
go get -u gorm.io/driver/sqlite # 示例中使用 SQLite,你可以替换为其他数据库驱动

数据库连接与模型定义

首先,我们需要建立数据库连接和定义一个数据模型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
package main

import (
"fmt"
"gorm.io/gorm"
"gorm.io/driver/sqlite" // 示例使用 SQLite
"time"
)

// User 是一个 GORM 模型,映射到数据库中的 `users` 表
type User struct {
// gorm.Model 包含了常用的字段: ID, CreatedAt, UpdatedAt, DeletedAt
// ID 是自增主键
gorm.Model
Name string `gorm:"size:255;not null"` // 姓名,非空
Email string `gorm:"unique;not null"` // 邮箱,唯一且非空
Age uint // 年龄
Balance float64 `gorm:"default:0.0"` // 余额,默认值为 0.0
// 还可以添加其他字段,例如:
// IsActive bool
// Address string
}

// Global DB instance (全局 DB 实例,实际项目中通常通过依赖注入管理)
var DB *gorm.DB

func init() {
var err error
// 连接到 SQLite 数据库 (test.db)
DB, err = gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
if err != nil {
panic("failed to connect database: " + err.Error())
}

// 自动迁移模式,根据 User 结构体创建或更新数据库表
err = DB.AutoMigrate(&User{})
if err != nil {
panic("failed to auto migrate: " + err.Error())
}
fmt.Println("Database connected and migrated successfully!")
}

func main() {
// 这里可以调用各种操作函数进行演示
fmt.Println("\n--- 开始 GORM 操作演示 ---")

// 插入操作
fmt.Println("\n--- 插入 (Create) ---")
user1 := createUser("Alice", "[email protected]", 30)
createUser("Bob", "[email protected]", 25)
createUsersBatch([]User{
{Name: "Charlie", Email: "[email protected]", Age: 35},
{Name: "David", Email: "[email protected]", Age: 40},
})

// 查询操作
fmt.Println("\n--- 查询 (Retrieve) ---")
findUserByID(user1.ID)
findUserByEmail("[email protected]")
findAllUsers()
findUsersByName("Alice")
findUserWithConditions()
findPaginatedUsers(1, 2) // 第一页,每页2条

// 更新操作
fmt.Println("\n--- 更新 (Update) ---")
updateUserByID(user1.ID, "Alice Smith", "[email protected]")
updateUserColumnsByEmail("[email protected]", 26, 100.5)

// 删除操作
fmt.Println("\n--- 删除 (Delete) ---")
deleteUserByID(user1.ID) // 软删除
// hardDeleteUserByID(user1.ID) // 硬删除 (如果需要)

// 再次查询所有用户,看软删除效果
fmt.Println("\n--- 再次查询所有用户 (After Delete) ---")
findAllUsers()
}

1. 插入数据 (Create)

使用 DB.Create() 方法将新的记录插入到数据库中。

1.1 插入单条记录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// createUser 插入单条用户记录
func createUser(name, email string, age uint) *User {
newUser := User{
Name: name,
Email: email,
Age: age,
}
result := DB.Create(&newUser) // 传入结构体指针

if result.Error != nil {
fmt.Printf("Error creating user %s: %v\n", name, result.Error)
return nil
}
fmt.Printf("Successfully created user: %s (ID: %d), Rows Affected: %d\n", newUser.Name, newUser.ID, result.RowsAffected)
return &newUser // GORM 会将新记录的 ID 填充回结构体
}

使用示例:

1
2
user1 := createUser("Alice", "[email protected]", 30)
// 此时 user1.ID 会被 GORM 填充为新插入记录的自增 ID

1.2 批量插入记录

传递一个结构体切片给 DB.Create() 即可实现批量插入。

1
2
3
4
5
6
7
8
9
10
11
12
13
// createUsersBatch 批量插入用户记录
func createUsersBatch(users []User) {
result := DB.Create(&users) // 传入结构体切片指针

if result.Error != nil {
fmt.Printf("Error creating users batch: %v\n", result.Error)
return
}
fmt.Printf("Successfully created %d users in batch.\n", result.RowsAffected)
for _, user := range users {
fmt.Printf(" - %s (ID: %d)\n", user.Name, user.ID)
}
}

使用示例:

1
2
3
4
createUsersBatch([]User{
{Name: "Charlie", Email: "[email protected]", Age: 35},
{Name: "David", Email: "[email protected]", Age: 40},
})

2. 查询数据 (Retrieve)

GORM 提供了多种查询方法,可以灵活地检索数据。查询结果会赋值给 Go 语言的变量。

2.1 根据主键查询 (First / Find)

  • First(&dest, primaryKey): 查找主键匹配的第一个记录。如果没有找到记录,result.Error 会是 gorm.ErrRecordNotFound
  • Find(&dest, primaryKey): 查找主键匹配的所有记录。如果找到多个,只会赋值给切片。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// findUserByID 根据 ID 查询用户
func findUserByID(id uint) {
var user User
// First 会根据主键查找,如果找不到会返回 gorm.ErrRecordNotFound
result := DB.First(&user, id) // 传入结构体指针和主键值

if result.Error != nil {
if result.Error == gorm.ErrRecordNotFound {
fmt.Printf("User with ID %d not found.\n", id)
} else {
fmt.Printf("Error finding user by ID %d: %v\n", id, result.Error)
}
return
}
fmt.Printf("Found User by ID %d: Name=%s, Email=%s, Age=%d\n", user.ID, user.Name, user.Email, user.Age)
}

使用示例:

1
findUserByID(1)

2.2 根据条件查询 (Where)

使用 Where() 方法添加查询条件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
// findUserByEmail 根据邮箱查询用户
func findUserByEmail(email string) {
var user User
// Where 方法用于构建查询条件
result := DB.Where("email = ?", email).First(&user) // 使用占位符防止 SQL 注入

if result.Error != nil {
if result.Error == gorm.ErrRecordNotFound {
fmt.Printf("User with email %s not found.\n", email)
} else {
fmt.Printf("Error finding user by email %s: %v\n", email, result.Error)
}
return
}
fmt.Printf("Found User by Email %s: Name=%s, Age=%d\n", user.Email, user.Name, user.Age)
}

// findUsersByName 根据姓名模糊查询所有匹配的用户
func findUsersByName(name string) {
var users []User // 声明一个结构体切片来存储多条记录
// 使用 LIKE 进行模糊匹配
result := DB.Where("name LIKE ?", "%"+name+"%").Find(&users)

if result.Error != nil {
fmt.Printf("Error finding users by name %s: %v\n", name, result.Error)
return
}
if len(users) == 0 {
fmt.Printf("No users found with name containing '%s'.\n", name)
return
}
fmt.Printf("Found %d users with name containing '%s':\n", len(users), name)
for _, user := range users {
fmt.Printf(" - ID: %d, Name: %s, Email: %s\n", user.ID, user.Name, user.Email)
}
}

// findUserWithConditions 复杂条件查询
func findUserWithConditions() {
var user User
// 链式调用多个 Where 条件
result := DB.Where("age > ?", 20).Where("name LIKE ?", "%lice%").First(&user)

if result.Error != nil {
if result.Error == gorm.ErrRecordNotFound {
fmt.Println("No user found with age > 20 and name like '%lice%'.")
} else {
fmt.Printf("Error finding user with conditions: %v\n", result.Error)
}
return
}
fmt.Printf("Found User with conditions: ID=%d, Name=%s, Age=%d\n", user.ID, user.Name, user.Age)
}

使用示例:

1
2
findUserByEmail("[email protected]")
findUsersByName("li") // 查找名字中包含 "li" 的用户

2.3 查询所有记录 (Find)

不带 Where() 条件的 Find() 方法会查询表中的所有记录。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// findAllUsers 查询所有用户
func findAllUsers() {
var users []User // 声明一个结构体切片
result := DB.Find(&users)

if result.Error != nil {
fmt.Printf("Error finding all users: %v\n", result.Error)
return
}
if len(users) == 0 {
fmt.Println("No users found in the database.")
return
}
fmt.Printf("Found %d users:\n", len(users))
for _, user := range users {
fmt.Printf(" - ID: %d, Name: %s, Email: %s, Age: %d, CreatedAt: %s\n",
user.ID, user.Name, user.Email, user.Age, user.CreatedAt.Format("2006-01-02 15:04:05"))
}
}

使用示例:

1
findAllUsers()

2.4 排序、限制和偏移 (Order, Limit, Offset)

用于分页和排序查询结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// findPaginatedUsers 分页查询用户
func findPaginatedUsers(page, pageSize int) {
var users []User
offset := (page - 1) * pageSize // 计算偏移量

result := DB.Offset(offset).Limit(pageSize).Order("created_at desc").Find(&users)

if result.Error != nil {
fmt.Printf("Error finding paginated users (Page %d, Size %d): %v\n", page, pageSize, result.Error)
return
}
if len(users) == 0 {
fmt.Printf("No users found on page %d (size %d).\n", page, pageSize)
return
}
fmt.Printf("Found %d users on Page %d (Size %d):\n", len(users), page, pageSize)
for _, user := range users {
fmt.Printf(" - ID: %d, Name: %s, CreatedAt: %s\n", user.ID, user.Name, user.CreatedAt.Format("2006-01-02 15:04:05"))
}
}

使用示例:

1
findPaginatedUsers(1, 2) // 查询第一页,每页2条

3. 更新数据 (Update)

GORM 提供了多种更新数据的方法。

3.1 更新单个字段 (Update)

Update() 方法用于更新模型的单个字段。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// updateUserByID 根据 ID 更新用户姓名和邮箱
func updateUserByID(id uint, newName, newEmail string) {
var user User
// 首先找到记录
result := DB.First(&user, id)
if result.Error != nil {
fmt.Printf("Error finding user ID %d for update: %v\n", id, result.Error)
return
}

// 更新单个字段
// DB.Model(&user).Update("name", newName) // 更新单个字段
// DB.Model(&user).Update("email", newEmail)

// 更新多个字段 (使用 map 或结构体)
result = DB.Model(&user).Updates(User{Name: newName, Email: newEmail}) // 使用结构体更新
// 或者
// result = DB.Model(&user).Updates(map[string]interface{}{"name": newName, "email": newEmail}) // 使用 map 更新

if result.Error != nil {
fmt.Printf("Error updating user ID %d: %v\n", id, result.Error)
return
}
fmt.Printf("Successfully updated user ID %d to Name: %s, Email: %s, Rows Affected: %d\n", id, newName, newEmail, result.RowsAffected)
}

使用示例:

1
updateUserByID(1, "Alice Smith", "[email protected]")

3.2 更新多个字段 (Updates)

Updates() 方法用于更新模型的多个字段。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// updateUserColumnsByEmail 根据邮箱更新用户年龄和余额
func updateUserColumnsByEmail(email string, newAge uint, newBalance float64) {
// 使用 Where 找到记录,然后用 Updates 更新
result := DB.Model(&User{}).Where("email = ?", email).Updates(map[string]interface{}{
"age": newAge,
"balance": newBalance,
})

if result.Error != nil {
fmt.Printf("Error updating user by email %s: %v\n", email, result.Error)
return
}
if result.RowsAffected == 0 {
fmt.Printf("No user found with email %s to update.\n", email)
} else {
fmt.Printf("Successfully updated user with email %s, Rows Affected: %d\n", email, result.RowsAffected)
}
}

使用示例:

1
updateUserColumnsByEmail("[email protected]", 26, 100.5)

3.3 计数器更新 (Inc / Dec)

用于原子性地增加或减少字段值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// incrementUserAge 增加用户年龄
func incrementUserAge(id uint, delta int) {
var user User
result := DB.First(&user, id)
if result.Error != nil {
fmt.Printf("Error finding user ID %d for age increment: %v\n", id, result.Error)
return
}

result = DB.Model(&user).Update("age", gorm.Expr("age + ?", delta))
// 或者使用 Inc/Dec 方法 (GORM v1.20.0+):
// result = DB.Model(&user).Inc("age", delta)

if result.Error != nil {
fmt.Printf("Error incrementing age for user ID %d: %v\n", id, result.Error)
return
}
fmt.Printf("Successfully incremented age for user ID %d, Rows Affected: %d\n", id, result.RowsAffected)
}

4. 删除数据 (Delete)

GORM 支持软删除和硬删除。

4.1 软删除 (Soft Delete)

如果你的模型嵌入了 gorm.Model(或包含 DeletedAt gorm.DeletedAt 字段),GORM 默认会进行软删除。它不会真正删除记录,而是将 DeletedAt 字段设置为当前时间。

软删除的记录在常规查询中不会被检索到,除非使用 Unscoped() 方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// deleteUserByID 软删除用户
func deleteUserByID(id uint) {
var user User
// 使用 Delete 方法进行软删除
result := DB.Delete(&user, id) // 传入结构体指针和主键值

if result.Error != nil {
fmt.Printf("Error soft deleting user ID %d: %v\n", id, result.Error)
return
}
fmt.Printf("Successfully soft deleted user ID %d, Rows Affected: %d\n", id, result.RowsAffected)
}

// findDeletedUserByID 查找被软删除的用户 (需要 Unscoped)
func findDeletedUserByID(id uint) {
var user User
// 使用 Unscoped() 方法可以查询被软删除的记录
result := DB.Unscoped().First(&user, id)

if result.Error != nil {
if result.Error == gorm.ErrRecordNotFound {
fmt.Printf("User with ID %d (including deleted) not found.\n", id)
} else {
fmt.Printf("Error finding deleted user by ID %d: %v\n", id, result.Error)
}
return
}
fmt.Printf("Found Deleted User by ID %d: Name=%s, DeletedAt=%s\n", user.ID, user.Name, user.DeletedAt.Format("2006-01-02 15:04:05"))
}

使用示例:

1
2
3
deleteUserByID(1) // 软删除 ID 为 1 的用户
findUserByID(1) // 此时应该找不到 ID 为 1 的用户
findDeletedUserByID(1) // 可以找到被软删除的用户

4.2 硬删除 (Permanent Delete)

如果你想彻底从数据库中删除记录,可以使用 Unscoped().Delete()

1
2
3
4
5
6
7
8
9
10
11
12
// hardDeleteUserByID 硬删除用户
func hardDeleteUserByID(id uint) {
var user User
// 使用 Unscoped().Delete() 进行硬删除
result := DB.Unscoped().Delete(&user, id)

if result.Error != nil {
fmt.Printf("Error hard deleting user ID %d: %v\n", id, result.Error)
return
}
fmt.Printf("Successfully hard deleted user ID %d, Rows Affected: %d\n", id, result.RowsAffected)
}

使用示例:

1
// hardDeleteUserByID(1) // 硬删除 ID 为 1 的用户

总结

本文档涵盖了 GORM 中最常用的数据库操作方法。GORM 还有许多高级功能,例如:

  • 关联 (Associations):一对一、一对多、多对多关系。
  • 事务 (Transactions):确保一系列数据库操作的原子性。
  • 预加载 (Preloading):一次性加载关联数据以避免 N+1 查询问题。
  • Hook (钩子):在创建、更新、删除等操作前后执行自定义逻辑。
  • 原生 SQL (Raw SQL):当 ORM 无法满足需求时,直接执行 SQL 语句。

查阅 GORM 官方文档 以获取更多详细信息和高级用法。