vault backup: 2026-06-07 12:14:39

This commit is contained in:
2026-06-07 12:14:39 +08:00
parent 1a72bc4d82
commit 7ce9b83218
61 changed files with 8409 additions and 14429 deletions
+134 -208
View File
@@ -1,274 +1,200 @@
---
tags:
- Go
- golang
- gorm
- mysql
- 数据库
tags: [go, golang, gorm, ORM, 数据库]
create time: 2026-06-07 15:25
---
# GORM
GORM是一个功能强大的Go语言ORM库,提供了简洁的API来操作数据库。本文将详细介绍如何使用GORM与MySQL数据库进行交互。
## 概述
## 安装GORM和MySQL驱动
GORM 是 Go 生态中最流行的 ORM 库,提供简洁的链式 API 操作数据库。本文从连接配置到 CRUD、关联关系和事务,覆盖日常开发的核心用法。
首先,我们需要安装GORM和MySQL驱动。可以使用以下命令:
## 正文
### 安装
```bash
go get -u gorm.io/gorm
go get -u gorm.io/driver/mysql
go get -gorm.io/gorm
go get -gorm.io/driver/mysql # MySQL 驱动(按需选择)
```
## 连接数据库
在使用GORM连接MySQL数据库时,我们需要提供数据源名称(DSN),其中包含用户名、密码、主机地址、端口号和数据库名称等信息。
### 连接数据库
```go
package main
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
"log"
)
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 {
log.Fatalf("failed to connect database: %v", err)
}
}
```
## 初始化连接
GORM会自动管理数据库连接池,因此我们不需要手动设置最大连接数和最大空闲连接数。不过,如果有特殊需求,可以通过`sql.DB`对象进行设置。
```go
sqlDB, err := db.DB()
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{})
if err != nil {
log.Fatalf("failed to get database object: %v", err)
log.Fatal("failed to connect database:", err)
}
// 设置最大打开连接数
sqlDB.SetMaxOpenConns(100)
// 设置最大空闲连接数
sqlDB.SetMaxIdleConns(10)
// 可选:调优连接池
sqlDB, _ := db.DB()
sqlDB.SetMaxOpenConns(100) // 最大连接数
sqlDB.SetMaxIdleConns(10) // 最大空闲连接
```
## 定义模型
> [!tip] 💡 性能建议
> - `SetMaxOpenConns` 应根据并发量和请求耗时调整——IO 密集型可适当放大
> - `SetMaxIdleConns` 一般设为 MaxOpenConns 的 1/4~1/2
> - 连接泄漏(未正确关闭)会导致连接池耗尽,务必注意
在GORM中,我们通过定义结构体来映射数据库表。每个字段对应表中的一列。
### 模型定义
```go
// User 结构体映射到数据库中的users表
type User struct {
ID uint `gorm:"primaryKey;column:id"` // ID为主键
Name string `gorm:"size:255;column:name"` // Name字段最大长度为255
Age int `gorm:"column:age"` // Age字段
ID uint `gorm:"primaryKey"`
Name string `gorm:"size:255;not null"`
Age int
Email string `gorm:"uniqueIndex"`
CreatedAt time.Time
}
// 表名默认为复数形式 "users",可通过 TableName 自定义
func (User) TableName() string {
return "app_users"
}
```
## 自动迁移
> [!info] ℹ️ GORM 命名约定
> - 表名:结构体名的蛇形复数(User → users)
> - 字段名:驼峰转蛇形(UserID → user_id)
> - 主键:自动使用 ID / uuid / uid 作为主键并自增
> - 外键:关联字段名 + `_id`
GORM提供了自动迁移功能,可以根据模型结构自动创建或更新数据库表。
### CRUD 操作
#### 创建
```go
// 自动迁移User结构体
db.AutoMigrate(&User{})
```
## CRUD操作
### 创建记录
使用`Create`方法可以向数据库中插入一条新记录。
```go
// 创建一个新的User记录
user := User{Name: "张三", Age: 25}
result := db.Create(&user)
if result.Error != nil {
log.Fatalf("failed to create user: %v", result.Error)
}
result := db.Create(&user) // 自动回填 user.ID
fmt.Println(user.ID) // 查看插入后的 ID
```
### 查询记录
#### 单行查询
使用`First`方法可以查询符合条件的第一条记录。
#### 查询
```go
// 查询ID为1的User记录
var user User
result := db.First(&user, 1) // 根据主键查询
if result.Error != nil {
log.Printf("failed to find user: %v", result.Error)
}
// 根据主键查询
var u1 User
db.First(&u1, 1) // SELECT * FROM users WHERE id = 1
// 条件查询
var u2 []User
db.Where("age > ?", 18).Find(&u2)
// 链式组合
db.Where("name LIKE ?", "%张%").Order("age desc").Limit(10).Find(&u2)
```
#### 多行查询
> [!question] 💭 ORM vs 原生 SQL
> GORM 的链式调用确实优雅,但在复杂查询场景下是否值得牺牲 SQL 的表达能力?思考你的项目中哪些查询可以用 GORM 表达,哪些需要手写 SQL。
使用`Find`方法可以查询符合条件的多条记录。
#### 更新
```go
// 查询年龄大于20的所有User记录
var users []User
result := db.Where("age > ?", 20).Find(&users)
if result.Error != nil {
log.Printf("failed to find users: %v", result.Error)
}
// 更新单个字段
db.Model(&user).Update("name", "李四")
// 更新多个字段
db.Model(&user).Updates(User{Name: "王五", Age: 30})
// 全量更新(注意:会忽略零值字段!)
db.Save(&user)
```
### 更新记录
> [!warning] ⚠️ Save 的零值陷阱
> `Save()` 会用结构体的所有字段执行 UPDATE。如果某个字段是零值(如 `Age: 0`),会被更新为 0!建议使用 `Updates` 并只传需要更新的字段。
使用`Save`方法可以更新记录。
#### 删除
```go
// 更新User记录的Age字段
user.Age = 26
result := db.Save(&user)
if result.Error != nil {
log.Fatalf("failed to update user: %v", result.Error)
}
db.Delete(&user, 1) // 软删除(需启用)或硬删除
db.Where("age < ?", 18).Delete(&User{}) // 批量删除
```
### 删除记录
> [!note] 📝 软删除
> GORM 默认启用软删除——调用 Delete 后记录不会被物理删除,而是设置 `deleted_at` 时间戳。如需物理删除:`db.Unscoped().Delete(&user)`
使用`Delete`方法可以删除记录。
### 关联关系
```go
// 删除User记录
result := db.Delete(&user)
if result.Error != nil {
log.Fatalf("failed to delete user: %v", result.Error)
type Profile struct {
ID uint
UserID uint `gorm:"index"`
Bio string
}
type Post struct {
ID uint
AuthorID uint `gorm:"index"`
Title string
Author User `gorm:"foreignKey:AuthorID"` // 指定外键
}
// 预加载关联数据
db.Preload("Profile").First(&user, 1)
// SELECT * FROM profiles WHERE user_id = 1
```
## 事务支持
> [!tip] 💡 N+1 问题
> 在循环中逐个查询关联数据会产生 N+1 问题。始终使用 `Preload` 一次性加载:
> ```go
> // ❌ N+1
> for _, post := range posts {
> db.First(&post.Author)
> }
>
> // ✅ 一次加载
> db.Preload("Author").Find(&posts)
> ```
GORM支持事务操作,可以通过`Transaction`方法来执行事务。
### 事务
```go
// 使用事务创建两个User记录
err := db.Transaction(func(tx *gorm.DB) error {
if err := tx.Create(&User{Name: "李四", Age: 30}).Error; err != nil {
if err := tx.Create(&User{Name: "Alice"}).Error; err != nil {
return err // 返回非零错误触发回滚
}
if err := tx.Create(&User{Name: "Bob"}).Error; err != nil {
return err
}
if err := tx.Create(&User{Name: "王五", Age: 35}).Error; err != nil {
return err
}
return nil
return nil // 返回 nil 则提交
})
if err != nil {
log.Fatalf("transaction failed: %v", err)
}
```
## 预处理和SQL注入
> [!warning] ⚠️ 事务中的常见错误
> - 不要在事务中使用全局 `db` 变量,应使用传入的 `tx`
> - 事务内的所有操作都必须检查 `.Error`
> - 事务必须在同一个连接上执行(GORM 默认保证这一点)
GORM会自动处理SQL注入问题,因此我们不需要手动拼接SQL语句。所有的查询条件都可以通过链式调用来实现。
### ORM vs NORM
```go
// 查询Name为"张三"的User记录
db.Where("name = ?", "张三").First(&user)
```
> [!question] 💭 思考
> GORM 属于 ORM(对象关系映射),而 Gin 搭配 sqlx 属于 NORM(无映射)。什么时候该用 ORM,什么时候该用 NORM?
## 完整示例
以下是一个完整的示例代码,展示了如何使用GORM连接MySQL数据库并进行CRUD操作。
```go
package main
| 维度 | ORM (GORM) | NORM (sqlx/raw SQL) |
|------|-----------|---------------------|
| 开发效率 | 高(链式 API) | 低(需写 SQL) |
| 类型安全 | 编译期部分保障 | 完全依赖 SQL 编写 |
| 复杂查询 | 有限支持 | 完全自由 |
| 性能 | 有额外开销 | 最优 |
| 学习曲线 | 低 | 需熟悉 SQL |
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"log"
)
> [!tip] 💡 混合策略
> 大多数项目可以采用混合模式:简单 CRUD 用 GORM,复杂报表/分析查询用原生 SQL:
> ```go
> // 简单查询用 GORM
> db.Find(&users)
>
> // 复杂查询用原生 SQL
> var results []struct {
> Department string
> AvgAge float64
> }
> db.Raw(`SELECT department, AVG(age) as avg_age FROM users GROUP BY department`).Scan(&results)
> ```
// User 结构体映射到数据库中的users表
type User struct {
ID uint `gorm:"primaryKey;column:id"` // ID为主键
Name string `gorm:"size:255;column:name"` // Name字段最大长度为255
Age int `gorm:"column:age"` // Age字段
}
## 关联笔记
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 {
log.Fatalf("failed to connect database: %v", err)
}
// 自动迁移User结构体
db.AutoMigrate(&User{})
// 创建一个新的User记录
user := User{Name: "张三", Age: 25}
result := db.Create(&user)
if result.Error != nil {
log.Fatalf("failed to create user: %v", result.Error)
}
// 查询ID为1的User记录
var queriedUser User
result = db.First(&queriedUser, user.ID) // 根据主键查询
if result.Error != nil {
log.Printf("failed to find user: %v", result.Error)
} else {
fmt.Printf("Queried User: %+v\n", queriedUser)
}
// 更新User记录的Age字段
queriedUser.Age = 26
result = db.Save(&queriedUser)
if result.Error != nil {
log.Fatalf("failed to update user: %v", result.Error)
}
// 删除User记录
result = db.Delete(&queriedUser)
if result.Error != nil {
log.Fatalf("failed to delete user: %v", result.Error)
}
}
```
### 程序输出
假设数据库连接成功,程序将输出如下信息:
```
Queried User: {ID:1 Name:张三 Age:25}
```
这表示程序成功创建、查询、更新并删除了一条用户记录。
### 代码说明
1. **连接数据库**:使用`gorm.Open`方法连接MySQL数据库,数据源名称(DSN)中包含了数据库连接的必要信息。
2. **自动迁移**:使用`AutoMigrate`方法根据`User`结构体自动创建或更新数据库表。
3. **创建记录**:使用`Create`方法向数据库中插入一条新记录。
4. **查询记录**:使用`First`方法查询符合条件的第一条记录。
5. **更新记录**:使用`Save`方法更新记录。
6. **删除记录**:使用`Delete`方法删除记录。
---
## 小结
这篇文章简单介绍了GORM的基本使用方法,包括连接数据库、定义模型、CRUD操作、事务支持等内容。希望对您有所帮助!,如需了解GORM详细的用法,可以参考:[GORM中文官网](https://gorm.io/zh_CN/docs/)
- [[hzh/GolangStar/Go语言框架/gin]] — Gin + GORM 是 Go Web 全栈的经典搭档
- [[hzh/GolangStar/Go语言进阶/Context]] — HTTP 请求生命周期与数据库事务结合